mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-17 02:31:34 +08:00
fix(router): fixes navChanged()
This commit is contained in:
10
core/src/components.d.ts
vendored
10
core/src/components.d.ts
vendored
@ -95,6 +95,7 @@ import {
|
|||||||
} from './components/nav/view-controller';
|
} from './components/nav/view-controller';
|
||||||
import {
|
import {
|
||||||
RouteID,
|
RouteID,
|
||||||
|
RouterDirection,
|
||||||
RouterEventDetail,
|
RouterEventDetail,
|
||||||
RouteWrite,
|
RouteWrite,
|
||||||
} from './components/router/utils/interfaces';
|
} from './components/router/utils/interfaces';
|
||||||
@ -3502,7 +3503,6 @@ declare global {
|
|||||||
'canGoBack': (view?: ViewController) => boolean;
|
'canGoBack': (view?: ViewController) => boolean;
|
||||||
'delegate': FrameworkDelegate;
|
'delegate': FrameworkDelegate;
|
||||||
'getActive': () => ViewController;
|
'getActive': () => ViewController;
|
||||||
'getAllChildNavs': () => any[];
|
|
||||||
'getByIndex': (index: number) => ViewController;
|
'getByIndex': (index: number) => ViewController;
|
||||||
'getPrevious': (view?: ViewController) => ViewController;
|
'getPrevious': (view?: ViewController) => ViewController;
|
||||||
'getRouteId': () => RouteID;
|
'getRouteId': () => RouteID;
|
||||||
@ -3546,7 +3546,7 @@ declare global {
|
|||||||
export interface IonNavAttributes extends HTMLAttributes {
|
export interface IonNavAttributes extends HTMLAttributes {
|
||||||
'animated'?: boolean;
|
'animated'?: boolean;
|
||||||
'delegate'?: FrameworkDelegate;
|
'delegate'?: FrameworkDelegate;
|
||||||
'onIonNavChanged'?: (event: CustomEvent) => void;
|
'onIonNavChanged'?: (event: CustomEvent<void>) => void;
|
||||||
'root'?: any;
|
'root'?: any;
|
||||||
'rootParams'?: any;
|
'rootParams'?: any;
|
||||||
'swipeBackEnabled'?: boolean;
|
'swipeBackEnabled'?: boolean;
|
||||||
@ -4733,8 +4733,8 @@ declare global {
|
|||||||
declare global {
|
declare global {
|
||||||
interface HTMLIonRouterElement extends HTMLStencilElement {
|
interface HTMLIonRouterElement extends HTMLStencilElement {
|
||||||
'base': string;
|
'base': string;
|
||||||
'navChanged': (isPop: boolean) => Promise<boolean>;
|
'navChanged': (direction: RouterDirection) => Promise<boolean>;
|
||||||
'push': (url: string, backDirection?: boolean) => Promise<boolean>;
|
'push': (url: string, direction?: RouterDirection) => Promise<boolean>;
|
||||||
'useHash': boolean;
|
'useHash': boolean;
|
||||||
}
|
}
|
||||||
var HTMLIonRouterElement: {
|
var HTMLIonRouterElement: {
|
||||||
@ -5977,7 +5977,7 @@ declare global {
|
|||||||
* Emitted when the tab changes.
|
* Emitted when the tab changes.
|
||||||
*/
|
*/
|
||||||
'onIonChange'?: (event: CustomEvent) => void;
|
'onIonChange'?: (event: CustomEvent) => void;
|
||||||
'onIonNavChanged'?: (event: CustomEvent<any>) => void;
|
'onIonNavChanged'?: (event: CustomEvent<void>) => void;
|
||||||
'scrollable'?: boolean;
|
'scrollable'?: boolean;
|
||||||
/**
|
/**
|
||||||
* If true, the tabbar
|
* If true, the tabbar
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Component, Element, Prop } from '@stencil/core';
|
import { Component, Element, Prop } from '@stencil/core';
|
||||||
import { Config } from '../../index';
|
import { Config } from '../../index';
|
||||||
import { openURL } from '../../utils/theme';
|
import { openURL } from '../../utils/theme';
|
||||||
|
import { RouterDirection } from '../router/utils/interfaces';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
tag: 'ion-back-button',
|
tag: 'ion-back-button',
|
||||||
@ -41,7 +42,7 @@ export class BackButton {
|
|||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
nav.pop();
|
nav.pop();
|
||||||
} else if (this.defaultHref) {
|
} else if (this.defaultHref) {
|
||||||
openURL(this.defaultHref, ev, true);
|
openURL(this.defaultHref, ev, RouterDirection.Back);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,8 +38,4 @@ export class HideWhen implements DisplayWhen {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,8 +34,8 @@ export const enum ViewState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const enum NavDirection {
|
export const enum NavDirection {
|
||||||
back = 'back',
|
Back = 'back',
|
||||||
forward = 'forward'
|
Forward = 'forward'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NavParams = {[key: string]: any};
|
export type NavParams = {[key: string]: any};
|
||||||
@ -58,6 +58,7 @@ export interface NavOptions {
|
|||||||
keyboardClose?: boolean;
|
keyboardClose?: boolean;
|
||||||
progressAnimation?: boolean;
|
progressAnimation?: boolean;
|
||||||
ev?: any;
|
ev?: any;
|
||||||
|
updateURL?: boolean;
|
||||||
delegate?: FrameworkDelegate;
|
delegate?: FrameworkDelegate;
|
||||||
viewIsReady?: () => Promise<any>;
|
viewIsReady?: () => Promise<any>;
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
|
|
||||||
import { ViewController, isViewController } from './view-controller';
|
import { ViewController, isViewController } from './view-controller';
|
||||||
import { Animation, Config, DomController, FrameworkDelegate, GestureDetail, NavOutlet } from '../..';
|
import { Animation, Config, DomController, FrameworkDelegate, GestureDetail, NavOutlet } from '../..';
|
||||||
import { RouteID, RouteWrite } from '../router/utils/interfaces';
|
import { RouteID, RouteWrite, RouterDirection } from '../router/utils/interfaces';
|
||||||
import { AnimationOptions, ViewLifecycle, lifecycle, transition } from '../../utils/transition';
|
import { AnimationOptions, ViewLifecycle, lifecycle, transition } from '../../utils/transition';
|
||||||
import { assert } from '../../utils/helpers';
|
import { assert } from '../../utils/helpers';
|
||||||
|
|
||||||
@ -25,7 +25,6 @@ import mdTransitionAnimation from './animations/md.transition';
|
|||||||
})
|
})
|
||||||
export class NavControllerBase implements NavOutlet {
|
export class NavControllerBase implements NavOutlet {
|
||||||
|
|
||||||
private _children: NavControllerBase[] = [];
|
|
||||||
private _ids = -1;
|
private _ids = -1;
|
||||||
private _init = false;
|
private _init = false;
|
||||||
private _queue: TransitionInstruction[] = [];
|
private _queue: TransitionInstruction[] = [];
|
||||||
@ -64,7 +63,7 @@ export class NavControllerBase implements NavOutlet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event() ionNavChanged: EventEmitter;
|
@Event() ionNavChanged: EventEmitter<void>;
|
||||||
|
|
||||||
componentWillLoad() {
|
componentWillLoad() {
|
||||||
this.id = 'n' + (++ctrlIds);
|
this.id = 'n' + (++ctrlIds);
|
||||||
@ -209,27 +208,31 @@ export class NavControllerBase implements NavOutlet {
|
|||||||
|
|
||||||
let resolve: (result: RouteWrite) => void;
|
let resolve: (result: RouteWrite) => void;
|
||||||
const promise = new Promise<RouteWrite>((r) => resolve = r);
|
const promise = new Promise<RouteWrite>((r) => resolve = r);
|
||||||
|
let finish: Promise<boolean>;
|
||||||
const commonOpts: NavOptions = {
|
const commonOpts: NavOptions = {
|
||||||
|
updateURL: false,
|
||||||
viewIsReady: () => {
|
viewIsReady: () => {
|
||||||
let markVisible;
|
let mark: Function;
|
||||||
const p = new Promise(r => markVisible = r);
|
const p = new Promise(r => mark = r);
|
||||||
resolve({
|
resolve({
|
||||||
changed: true,
|
changed: true,
|
||||||
element: this.getActive().element,
|
element: this.getActive().element,
|
||||||
markVisible
|
markVisible: async () => {
|
||||||
|
mark();
|
||||||
|
await finish;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (viewController) {
|
if (viewController) {
|
||||||
this.popTo(viewController, {...commonOpts, direction: NavDirection.back});
|
finish = this.popTo(viewController, {...commonOpts, direction: NavDirection.Back});
|
||||||
} else if (direction === 1) {
|
} else if (direction === 1) {
|
||||||
this.push(id, params, commonOpts);
|
finish = this.push(id, params, commonOpts);
|
||||||
} else if (direction === -1) {
|
} else if (direction === -1) {
|
||||||
this.setRoot(id, params, {...commonOpts, direction: NavDirection.back, animate: true});
|
finish = this.setRoot(id, params, {...commonOpts, direction: NavDirection.Back, animate: true});
|
||||||
} else {
|
} else {
|
||||||
this.setRoot(id, params, commonOpts);
|
finish = this.setRoot(id, params, commonOpts);
|
||||||
}
|
}
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
@ -244,11 +247,6 @@ export class NavControllerBase implements NavOutlet {
|
|||||||
} : undefined;
|
} : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Method()
|
|
||||||
getAllChildNavs(): any[] {
|
|
||||||
return this._children.slice();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Method()
|
@Method()
|
||||||
canGoBack(view = this.getActive()): boolean {
|
canGoBack(view = this.getActive()): boolean {
|
||||||
return !!(view && this.getPrevious(view));
|
return !!(view && this.getPrevious(view));
|
||||||
@ -331,13 +329,6 @@ export class NavControllerBase implements NavOutlet {
|
|||||||
}
|
}
|
||||||
this._init = true;
|
this._init = true;
|
||||||
|
|
||||||
const isPop = result.direction === NavDirection.back;
|
|
||||||
if (this.useRouter) {
|
|
||||||
const router = document.querySelector('ion-router');
|
|
||||||
router && router.navChanged(isPop);
|
|
||||||
}
|
|
||||||
this.ionNavChanged.emit({isPop});
|
|
||||||
|
|
||||||
if (ti.done) {
|
if (ti.done) {
|
||||||
ti.done(
|
ti.done(
|
||||||
result.hasCompleted,
|
result.hasCompleted,
|
||||||
@ -348,6 +339,18 @@ export class NavControllerBase implements NavOutlet {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
ti.resolve(result.hasCompleted);
|
ti.resolve(result.hasCompleted);
|
||||||
|
|
||||||
|
if (ti.opts.updateURL !== false && this.useRouter) {
|
||||||
|
const router = document.querySelector('ion-router');
|
||||||
|
if (router) {
|
||||||
|
const direction = (result.direction === NavDirection.Back)
|
||||||
|
? RouterDirection.Back
|
||||||
|
: RouterDirection.Forward;
|
||||||
|
|
||||||
|
router && router.navChanged(direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.ionNavChanged.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _failed(rejectReason: any, ti: TransitionInstruction) {
|
private _failed(rejectReason: any, ti: TransitionInstruction) {
|
||||||
@ -528,7 +531,7 @@ export class NavControllerBase implements NavOutlet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// default the direction to "back"
|
// default the direction to "back"
|
||||||
opts.direction = opts.direction || NavDirection.back;
|
opts.direction = opts.direction || NavDirection.Back;
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalBalance = this._views.length + (insertViews ? insertViews.length : 0) - (removeCount ? removeCount : 0);
|
const finalBalance = this._views.length + (insertViews ? insertViews.length : 0) - (removeCount ? removeCount : 0);
|
||||||
@ -556,7 +559,7 @@ export class NavControllerBase implements NavOutlet {
|
|||||||
|
|
||||||
if (ti.enteringRequiresTransition) {
|
if (ti.enteringRequiresTransition) {
|
||||||
// default to forward if not already set
|
// default to forward if not already set
|
||||||
opts.direction = opts.direction || NavDirection.forward;
|
opts.direction = opts.direction || NavDirection.Forward;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -757,7 +760,7 @@ export class NavControllerBase implements NavOutlet {
|
|||||||
|
|
||||||
// default the direction to "back";
|
// default the direction to "back";
|
||||||
const opts: NavOptions = {
|
const opts: NavOptions = {
|
||||||
direction: NavDirection.back,
|
direction: NavDirection.Back,
|
||||||
progressAnimation: true
|
progressAnimation: true
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -809,7 +812,6 @@ export class NavControllerBase implements NavOutlet {
|
|||||||
canSwipeBack(): boolean {
|
canSwipeBack(): boolean {
|
||||||
return (
|
return (
|
||||||
this.swipeBackEnabled &&
|
this.swipeBackEnabled &&
|
||||||
this._children.length === 0 &&
|
|
||||||
!this.isTransitioning &&
|
!this.isTransitioning &&
|
||||||
this.canGoBack()
|
this.canGoBack()
|
||||||
);
|
);
|
||||||
|
@ -72,9 +72,6 @@ boolean
|
|||||||
#### getActive()
|
#### getActive()
|
||||||
|
|
||||||
|
|
||||||
#### getAllChildNavs()
|
|
||||||
|
|
||||||
|
|
||||||
#### getByIndex()
|
#### getByIndex()
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ export class RouterOutlet implements NavOutlet {
|
|||||||
async setRouteId(id: string, data: any, direction: number): Promise<RouteWrite> {
|
async setRouteId(id: string, data: any, direction: number): Promise<RouteWrite> {
|
||||||
const changed = await this.setRoot(id, data, {
|
const changed = await this.setRoot(id, data, {
|
||||||
duration: direction === 0 ? 0 : undefined,
|
duration: direction === 0 ? 0 : undefined,
|
||||||
direction: direction === -1 ? NavDirection.back : NavDirection.forward,
|
direction: direction === -1 ? NavDirection.Back : NavDirection.Forward,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
changed,
|
changed,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Build, Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core';
|
import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core';
|
||||||
import { Config, DomController } from '../../index';
|
import { Config, DomController } from '../../index';
|
||||||
import { flattenRouterTree, readRedirects, readRoutes } from './utils/parser';
|
import { flattenRouterTree, readRedirects, readRoutes } from './utils/parser';
|
||||||
import { readNavState, writeNavState } from './utils/dom';
|
import { readNavState, writeNavState } from './utils/dom';
|
||||||
import { chainToPath, generatePath, parsePath, readPath, writePath } from './utils/path';
|
import { chainToPath, generatePath, parsePath, readPath, writePath } from './utils/path';
|
||||||
import { RouteChain, RouteRedirect, RouterEventDetail } from './utils/interfaces';
|
import { RouteChain, RouteRedirect, RouterDirection, RouterEventDetail } from './utils/interfaces';
|
||||||
import { routeRedirect, routerIDsToChain, routerPathToChain } from './utils/matching';
|
import { routeRedirect, routerIDsToChain, routerPathToChain } from './utils/matching';
|
||||||
import { printRoutes } from './utils/debug';
|
import { printRoutes } from './utils/debug';
|
||||||
|
|
||||||
@ -53,9 +53,7 @@ export class Router {
|
|||||||
const tree = readRoutes(this.el);
|
const tree = readRoutes(this.el);
|
||||||
this.routes = flattenRouterTree(tree);
|
this.routes = flattenRouterTree(tree);
|
||||||
|
|
||||||
if (Build.isDev) {
|
|
||||||
printRoutes(this.routes);
|
printRoutes(this.routes);
|
||||||
}
|
|
||||||
|
|
||||||
// schedule write
|
// schedule write
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
@ -75,17 +73,21 @@ export class Router {
|
|||||||
this.state++;
|
this.state++;
|
||||||
window.history.replaceState(this.state, document.title, document.location.href);
|
window.history.replaceState(this.state, document.title, document.location.href);
|
||||||
}
|
}
|
||||||
const direction = window.history.state >= this.state ? 1 : -1;
|
const direction = window.history.state >= this.state
|
||||||
this.writeNavStateRoot(this.getPath(), direction);
|
? RouterDirection.Forward
|
||||||
|
: RouterDirection.Back;
|
||||||
|
|
||||||
|
const path = this.getPath();
|
||||||
|
console.debug('[ion-router] URL changed -> update nav', path, direction);
|
||||||
|
this.writeNavStateRoot(path, direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Method()
|
@Method()
|
||||||
async navChanged(isPop: boolean): Promise<boolean> {
|
async navChanged(direction: RouterDirection): Promise<boolean> {
|
||||||
if (this.busy) {
|
if (this.busy) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
console.debug('[IN] nav changed -> update URL');
|
const { ids, outlet } = readNavState(document.body);
|
||||||
const { ids, pivot } = readNavState(document.body);
|
|
||||||
const chain = routerIDsToChain(ids, this.routes);
|
const chain = routerIDsToChain(ids, this.routes);
|
||||||
if (!chain) {
|
if (!chain) {
|
||||||
console.warn('no matching URL for ', ids.map(i => i.id));
|
console.warn('no matching URL for ', ids.map(i => i.id));
|
||||||
@ -93,31 +95,38 @@ export class Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const path = chainToPath(chain);
|
const path = chainToPath(chain);
|
||||||
this.setPath(path, isPop);
|
if (!path) {
|
||||||
|
console.warn('router could not match path because some required param is missing');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (chain.length > ids.length) {
|
console.debug('[ion-router] nav changed -> update URL', ids, path);
|
||||||
await this.writeNavState(pivot, chain.slice(ids.length), 0);
|
this.setPath(path, direction);
|
||||||
|
if (outlet) {
|
||||||
|
console.debug('[ion-router] updating nested outlet', outlet);
|
||||||
|
await this.writeNavState(outlet, chain, RouterDirection.None, ids.length);
|
||||||
}
|
}
|
||||||
this.emitRouteChange(path, null);
|
this.emitRouteChange(path, null);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Method()
|
@Method()
|
||||||
push(url: string, backDirection = false) {
|
push(url: string, direction = RouterDirection.Forward) {
|
||||||
const path = parsePath(url);
|
const path = parsePath(url);
|
||||||
this.setPath(path, backDirection);
|
this.setPath(path, direction);
|
||||||
|
|
||||||
return this.writeNavStateRoot(path, backDirection ? -1 : 1);
|
console.debug('[ion-router] URL pushed -> updating nav', url, direction);
|
||||||
|
return this.writeNavStateRoot(path, direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async writeNavStateRoot(path: string[], direction: number): Promise<boolean> {
|
private async writeNavStateRoot(path: string[], direction: RouterDirection): Promise<boolean> {
|
||||||
if (this.busy) {
|
if (this.busy) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const redirect = routeRedirect(path, this.redirects);
|
const redirect = routeRedirect(path, this.redirects);
|
||||||
let redirectFrom: string[] = null;
|
let redirectFrom: string[] = null;
|
||||||
if (redirect) {
|
if (redirect) {
|
||||||
this.setPath(redirect.to, true);
|
this.setPath(redirect.to, direction);
|
||||||
redirectFrom = redirect.from;
|
redirectFrom = redirect.from;
|
||||||
path = redirect.to;
|
path = redirect.to;
|
||||||
}
|
}
|
||||||
@ -129,24 +138,25 @@ export class Router {
|
|||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async writeNavState(node: any, chain: RouteChain, direction: number): Promise<boolean> {
|
private async writeNavState(node: any, chain: RouteChain, direction: RouterDirection, index = 0): Promise<boolean> {
|
||||||
if (this.busy) {
|
if (this.busy) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.busy = true;
|
this.busy = true;
|
||||||
const changed = await writeNavState(node, chain, 0, direction);
|
const changed = await writeNavState(node, chain, direction, index);
|
||||||
this.busy = false;
|
this.busy = false;
|
||||||
return changed;
|
return changed;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.busy = false;
|
this.busy = false;
|
||||||
throw e;
|
console.error(e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setPath(path: string[], isPop: boolean) {
|
private setPath(path: string[], direction: RouterDirection) {
|
||||||
this.state++;
|
this.state++;
|
||||||
writePath(window.history, this.base, this.useHash, path, isPop, this.state);
|
writePath(window.history, this.base, this.useHash, path, direction, this.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPath(): string[] | null {
|
private getPath(): string[] | null {
|
||||||
@ -154,6 +164,7 @@ export class Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private emitRouteChange(path: string[], redirectPath: string[]|null) {
|
private emitRouteChange(path: string[], redirectPath: string[]|null) {
|
||||||
|
console.debug('[ion-router] route changed', path);
|
||||||
const from = this.previousPath;
|
const from = this.previousPath;
|
||||||
const redirectedFrom = redirectPath ? generatePath(redirectPath) : null;
|
const redirectedFrom = redirectPath ? generatePath(redirectPath) : null;
|
||||||
const to = generatePath(path);
|
const to = generatePath(path);
|
||||||
|
@ -20,12 +20,12 @@ describe('readRoutes', () => {
|
|||||||
r4.appendChild(r6);
|
r4.appendChild(r6);
|
||||||
|
|
||||||
const expected: RouteTree = [
|
const expected: RouteTree = [
|
||||||
{ path: [''], id: 'main-page', children: [], params: {router: root} },
|
{ path: [''], id: 'main-page', children: [], params: undefined },
|
||||||
{ path: ['one-page'], id: 'one-page', children: [], params: {router: root} },
|
{ path: ['one-page'], id: 'one-page', children: [], params: undefined },
|
||||||
{ path: ['secondpage'], id: 'second-page', params: {router: root}, children: [
|
{ path: ['secondpage'], id: 'second-page', params: undefined, children: [
|
||||||
{ path: ['5', 'hola'], id: '4', params: {router: root}, children: [
|
{ path: ['5', 'hola'], id: '4', params: undefined, children: [
|
||||||
{ path: ['path', 'to', 'five'], id: '5', children: [], params: {router: root} },
|
{ path: ['path', 'to', 'five'], id: '5', children: [], params: undefined },
|
||||||
{ path: ['path', 'to', 'five2'], id: '6', children: [], params: {router: root} }
|
{ path: ['path', 'to', 'five2'], id: '6', children: [], params: undefined }
|
||||||
] }
|
] }
|
||||||
] }
|
] }
|
||||||
];
|
];
|
||||||
|
@ -78,7 +78,7 @@ describe('chainToPath', () => {
|
|||||||
{ id: '3', path: ['segment'], params: undefined },
|
{ id: '3', path: ['segment'], params: undefined },
|
||||||
{ id: '8', path: [':name'], params: undefined },
|
{ id: '8', path: [':name'], params: undefined },
|
||||||
];
|
];
|
||||||
expect(() => chainToPath(chain)).toThrowError('missing param name');
|
expect(chainToPath(chain)).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should raise an exception 2', () => {
|
it('should raise an exception 2', () => {
|
||||||
@ -86,7 +86,7 @@ describe('chainToPath', () => {
|
|||||||
{ id: '3', path: ['segment'], params: undefined },
|
{ id: '3', path: ['segment'], params: undefined },
|
||||||
{ id: '8', path: [':name', ':id'], params: {name: 'hey'} },
|
{ id: '8', path: [':name', ':id'], params: {name: 'hey'} },
|
||||||
];
|
];
|
||||||
expect(() => chainToPath(chain)).toThrowError('missing param id');
|
expect(chainToPath(chain)).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { NavOutlet, NavOutletElement, RouteChain, RouteID } from './interfaces';
|
import { NavOutlet, NavOutletElement, RouteChain, RouteID, RouterDirection } from './interfaces';
|
||||||
|
|
||||||
export async function writeNavState(root: HTMLElement|undefined, chain: RouteChain|null, index: number, direction: number): Promise<boolean> {
|
export async function writeNavState(root: HTMLElement|undefined, chain: RouteChain|null, direction: RouterDirection, index: number): Promise<boolean> {
|
||||||
// find next navigation outlet in the DOM
|
// find next navigation outlet in the DOM
|
||||||
const outlet = searchNavNode(root);
|
const outlet = searchNavNode(root);
|
||||||
|
|
||||||
@ -16,11 +16,11 @@ export async function writeNavState(root: HTMLElement|undefined, chain: RouteCha
|
|||||||
// if the outlet changed the page, reset navigation to neutral (no direction)
|
// if the outlet changed the page, reset navigation to neutral (no direction)
|
||||||
// this means nested outlets will not animate
|
// this means nested outlets will not animate
|
||||||
if (result.changed) {
|
if (result.changed) {
|
||||||
direction = 0;
|
direction = RouterDirection.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// recursivelly set nested outlets
|
// recursivelly set nested outlets
|
||||||
const changed = await writeNavState(result.element, chain, index + 1, direction);
|
const changed = await writeNavState(result.element, chain, direction, index + 1);
|
||||||
|
|
||||||
// once all nested outlets are visible let's make the parent visible too,
|
// once all nested outlets are visible let's make the parent visible too,
|
||||||
// using markVisible prevents flickering
|
// using markVisible prevents flickering
|
||||||
@ -30,14 +30,14 @@ export async function writeNavState(root: HTMLElement|undefined, chain: RouteCha
|
|||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readNavState(root: HTMLElement) {
|
export function readNavState(root: HTMLElement | null) {
|
||||||
const ids: RouteID[] = [];
|
const ids: RouteID[] = [];
|
||||||
let pivot: NavOutlet|null;
|
let outlet: NavOutlet|null;
|
||||||
let node: HTMLElement|undefined = root;
|
let node: HTMLElement|null = root;
|
||||||
while (true) {
|
while (true) {
|
||||||
pivot = searchNavNode(node);
|
outlet = searchNavNode(node);
|
||||||
if (pivot) {
|
if (outlet) {
|
||||||
const id = pivot.getRouteId();
|
const id = outlet.getRouteId();
|
||||||
if (id) {
|
if (id) {
|
||||||
node = id.element;
|
node = id.element;
|
||||||
id.element = undefined;
|
id.element = undefined;
|
||||||
@ -49,7 +49,7 @@ export function readNavState(root: HTMLElement) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {ids, pivot};
|
return {ids, outlet};
|
||||||
}
|
}
|
||||||
|
|
||||||
const QUERY = ':not([no-router]) ion-nav,:not([no-router]) ion-tabs, :not([no-router]) ion-router-outlet';
|
const QUERY = ':not([no-router]) ion-nav,:not([no-router]) ion-tabs, :not([no-router]) ion-router-outlet';
|
||||||
|
@ -10,6 +10,12 @@ export interface RouterEventDetail {
|
|||||||
to: string;
|
to: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const enum RouterDirection {
|
||||||
|
None = 0,
|
||||||
|
Forward = 1,
|
||||||
|
Back = -1,
|
||||||
|
}
|
||||||
|
|
||||||
export interface RouteRedirect {
|
export interface RouteRedirect {
|
||||||
from: string[];
|
from: string[];
|
||||||
to: string[]|undefined;
|
to: string[]|undefined;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { RouteChain, RouteNode, RouteRedirect, RouteTree } from './interfaces';
|
import { RouteChain, RouteNode, RouteRedirect, RouteTree } from './interfaces';
|
||||||
import { parsePath } from './path';
|
import { parsePath } from './path';
|
||||||
import { mergeParams } from './matching';
|
|
||||||
|
|
||||||
|
|
||||||
export function readRedirects(root: Element): RouteRedirect[] {
|
export function readRedirects(root: Element): RouteRedirect[] {
|
||||||
@ -16,16 +15,13 @@ export function readRedirects(root: Element): RouteRedirect[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function readRoutes(root: Element, node = root): RouteTree {
|
export function readRoutes(root: Element, node = root): RouteTree {
|
||||||
const commonParams = {
|
|
||||||
router: root
|
|
||||||
};
|
|
||||||
return (Array.from(node.children) as HTMLIonRouteElement[])
|
return (Array.from(node.children) as HTMLIonRouteElement[])
|
||||||
.filter(el => el.tagName === 'ION-ROUTE' && el.component)
|
.filter(el => el.tagName === 'ION-ROUTE' && el.component)
|
||||||
.map(el => {
|
.map(el => {
|
||||||
return {
|
return {
|
||||||
path: parsePath(readProp(el, 'url')),
|
path: parsePath(readProp(el, 'url')),
|
||||||
id: readProp(el, 'component').toLowerCase(),
|
id: readProp(el, 'component').toLowerCase(),
|
||||||
params: mergeParams(commonParams, el.componentProps),
|
params: el.componentProps,
|
||||||
children: readRoutes(root, el)
|
children: readRoutes(root, el)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { RouteChain } from './interfaces';
|
import { RouteChain, RouterDirection } from './interfaces';
|
||||||
|
|
||||||
export function generatePath(segments: string[]): string {
|
export function generatePath(segments: string[]): string {
|
||||||
const path = segments
|
const path = segments
|
||||||
@ -8,14 +8,14 @@ export function generatePath(segments: string[]): string {
|
|||||||
return '/' + path;
|
return '/' + path;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function chainToPath(chain: RouteChain): string[] {
|
export function chainToPath(chain: RouteChain): string[]|null {
|
||||||
const path = [];
|
const path = [];
|
||||||
for (const route of chain) {
|
for (const route of chain) {
|
||||||
for (const segment of route.path) {
|
for (const segment of route.path) {
|
||||||
if (segment[0] === ':') {
|
if (segment[0] === ':') {
|
||||||
const param = route.params && route.params[segment.slice(1)];
|
const param = route.params && route.params[segment.slice(1)];
|
||||||
if (!param) {
|
if (!param) {
|
||||||
throw new Error(`missing param ${segment.slice(1)}`);
|
return null;
|
||||||
}
|
}
|
||||||
path.push(param);
|
path.push(param);
|
||||||
} else if (segment !== '') {
|
} else if (segment !== '') {
|
||||||
@ -26,17 +26,16 @@ export function chainToPath(chain: RouteChain): string[] {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function writePath(history: History, base: string, usePath: boolean, path: string[], isPop: boolean, state: number) {
|
export function writePath(history: History, base: string, usePath: boolean, path: string[], direction: RouterDirection, state: number) {
|
||||||
path = [base, ...path];
|
path = [base, ...path];
|
||||||
let url = generatePath(path);
|
let url = generatePath(path);
|
||||||
if (usePath) {
|
if (usePath) {
|
||||||
url = '#' + url;
|
url = '#' + url;
|
||||||
}
|
}
|
||||||
if (isPop) {
|
if (direction === RouterDirection.Forward) {
|
||||||
// history.back();
|
|
||||||
history.replaceState(state, '', url);
|
|
||||||
} else {
|
|
||||||
history.pushState(state, '', url);
|
history.pushState(state, '', url);
|
||||||
|
} else {
|
||||||
|
history.replaceState(state, '', url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Build, Component, Element, Event, EventEmitter, Listen, Method, Prop, State } from '@stencil/core';
|
import { Build, Component, Element, Event, EventEmitter, Listen, Method, Prop, State } from '@stencil/core';
|
||||||
import { Config, NavOutlet } from '../../index';
|
import { Config, NavOutlet } from '../../index';
|
||||||
import { RouteID, RouteWrite } from '../router/utils/interfaces';
|
import { RouteID, RouteWrite, RouterDirection } from '../router/utils/interfaces';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -68,7 +68,7 @@ export class Tabs implements NavOutlet {
|
|||||||
* Emitted when the tab changes.
|
* Emitted when the tab changes.
|
||||||
*/
|
*/
|
||||||
@Event() ionChange: EventEmitter;
|
@Event() ionChange: EventEmitter;
|
||||||
@Event() ionNavChanged: EventEmitter<any>;
|
@Event() ionNavChanged: EventEmitter<void>;
|
||||||
|
|
||||||
componentWillLoad() {
|
componentWillLoad() {
|
||||||
this.useRouter = !!document.querySelector('ion-router') && !this.el.closest('[no-router]');
|
this.useRouter = !!document.querySelector('ion-router') && !this.el.closest('[no-router]');
|
||||||
@ -105,7 +105,8 @@ export class Tabs implements NavOutlet {
|
|||||||
}
|
}
|
||||||
await this.setActive(selectedTab);
|
await this.setActive(selectedTab);
|
||||||
await this.notifyRouter();
|
await this.notifyRouter();
|
||||||
return this.tabSwitch();
|
await this.tabSwitch();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Method()
|
@Method()
|
||||||
@ -119,7 +120,7 @@ export class Tabs implements NavOutlet {
|
|||||||
return {
|
return {
|
||||||
changed: true,
|
changed: true,
|
||||||
element: this.selectedTab,
|
element: this.selectedTab,
|
||||||
markVisible: () => { this.tabSwitch(); }
|
markVisible: () => this.tabSwitch(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,14 +221,14 @@ export class Tabs implements NavOutlet {
|
|||||||
return selectedTab.setActive();
|
return selectedTab.setActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
private tabSwitch(): boolean {
|
private tabSwitch() {
|
||||||
const selectedTab = this.selectedTab;
|
const selectedTab = this.selectedTab;
|
||||||
const leavingTab = this.leavingTab;
|
const leavingTab = this.leavingTab;
|
||||||
|
|
||||||
this.leavingTab = undefined;
|
this.leavingTab = undefined;
|
||||||
this.transitioning = false;
|
this.transitioning = false;
|
||||||
if (!selectedTab) {
|
if (!selectedTab) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedTab.selected = true;
|
selectedTab.selected = true;
|
||||||
@ -236,17 +237,15 @@ export class Tabs implements NavOutlet {
|
|||||||
leavingTab.active = false;
|
leavingTab.active = false;
|
||||||
}
|
}
|
||||||
this.ionChange.emit(selectedTab);
|
this.ionChange.emit(selectedTab);
|
||||||
this.ionNavChanged.emit({isPop: false});
|
this.ionNavChanged.emit();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private notifyRouter() {
|
private notifyRouter() {
|
||||||
if (this.useRouter) {
|
if (this.useRouter) {
|
||||||
const router = document.querySelector('ion-router');
|
const router = document.querySelector('ion-router');
|
||||||
if (router) {
|
if (router) {
|
||||||
return router.navChanged(false);
|
return router.navChanged(RouterDirection.Forward);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { CssClassMap } from '../index';
|
import { CssClassMap } from '../index';
|
||||||
|
import { RouterDirection } from '../components/router/utils/interfaces';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the mode and color classes for the component based on the classes passed in
|
* Create the mode and color classes for the component based on the classes passed in
|
||||||
@ -65,12 +66,12 @@ export function getClassMap(classes: string | undefined): CssClassMap {
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openURL(url: string, ev: Event, isPop = false) {
|
export function openURL(url: string, ev: Event, direction = RouterDirection.Forward) {
|
||||||
if (url && url[0] !== '#' && url.indexOf('://') === -1) {
|
if (url && url[0] !== '#' && url.indexOf('://') === -1) {
|
||||||
const router = document.querySelector('ion-router');
|
const router = document.querySelector('ion-router');
|
||||||
if (router) {
|
if (router) {
|
||||||
ev && ev.preventDefault();
|
ev && ev.preventDefault();
|
||||||
return router.componentOnReady().then(() => router.push(url, isPop));
|
return router.componentOnReady().then(() => router.push(url, direction));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -121,7 +121,7 @@ function fireDidEvents(enteringEl: HTMLElement, leavingEl: HTMLElement) {
|
|||||||
|
|
||||||
function setZIndex(enteringEl: HTMLElement, leavingEl: HTMLElement, direction: NavDirection) {
|
function setZIndex(enteringEl: HTMLElement, leavingEl: HTMLElement, direction: NavDirection) {
|
||||||
if (enteringEl) {
|
if (enteringEl) {
|
||||||
enteringEl.style.zIndex = (direction === NavDirection.back)
|
enteringEl.style.zIndex = (direction === NavDirection.Back)
|
||||||
? '99'
|
? '99'
|
||||||
: '101';
|
: '101';
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user