diff --git a/packages/core/scripts/test-components/src/components/page-one/page-one.tsx b/packages/core/scripts/test-components/src/components/page-one/page-one.tsx index fa460b3f67..dc7d31b48c 100644 --- a/packages/core/scripts/test-components/src/components/page-one/page-one.tsx +++ b/packages/core/scripts/test-components/src/components/page-one/page-one.tsx @@ -15,6 +15,7 @@ export class PageOne { page one + Ir a la page 2 ]; diff --git a/packages/core/src/components/nav/nav.tsx b/packages/core/src/components/nav/nav.tsx index 31156be6cf..f4fe878fc0 100644 --- a/packages/core/src/components/nav/nav.tsx +++ b/packages/core/src/components/nav/nav.tsx @@ -193,13 +193,25 @@ export class Nav implements PublicNav, NavOutlet { } @Method() - setRouteId(id: string, _: any = {}): Promise { - assert(this.useRouter, 'routing is disabled'); + setRouteId(id: string, _: any = {}, direction: number): Promise { const active = this.getActive(); if (active && active.component === id) { - return Promise.resolve(null); + return Promise.resolve(false); } - return this.setRoot(id); + if (direction === 1) { + return this.push(id).then(() => true); + } else if (direction === -1 && this._canGoBack(id)) { + return this.pop().then(() => true); + } + return this.setRoot(id).then(() => true); + } + + private _canGoBack(id: any) { + if (!this.canGoBack()) { + return false; + } + const view = this.views[this.views.length - 1]; + return view.component === id; } @Method() @@ -215,7 +227,7 @@ export class Nav implements PublicNav, NavOutlet { getContentElement(): HTMLElement { const active = getActiveImpl(this); if (active) { - active.element; + return active.element; } return null; } diff --git a/packages/core/src/components/router/router-utils.ts b/packages/core/src/components/router/router-utils.ts index 36e4815dae..1bc9cb5a51 100644 --- a/packages/core/src/components/router/router-utils.ts +++ b/packages/core/src/components/router/router-utils.ts @@ -1,5 +1,5 @@ export interface NavOutlet { - setRouteId(id: any, data?: any): Promise; + setRouteId(id: any, data: any, direction: number): Promise; getRouteId(): string; getContentElement(): HTMLElement | null; } @@ -28,22 +28,24 @@ export class RouterSegments { } } -export function writeNavState(root: HTMLElement, chain: RouterEntries, index = 0): Promise { +export function writeNavState(root: HTMLElement, chain: RouterEntries, index: number, direction: number): Promise { if (index >= chain.length) { return Promise.resolve(); } - const route = chain[index]; const node = breadthFirstSearch(root); if (!node) { return Promise.resolve(); } return node.componentOnReady() - .then(() => node.setRouteId(route.id, route.props)) - .then(() => { + .then(() => node.setRouteId(route.id, route.props, direction)) + .then(changed => { + if (changed) { + direction = 0; + } const nextEl = node.getContentElement(); if (nextEl) { - return writeNavState(nextEl, chain, index + 1); + return writeNavState(nextEl, chain, index + 1, direction); } return null; }); @@ -199,18 +201,20 @@ export function breadthFirstSearch(root: HTMLElement): NavOutletElement | null { return null; } -export function writePath(history: History, base: string, usePath: boolean, path: string[], isPop: boolean) { +export function writePath(history: History, base: string, usePath: boolean, path: string[], isPop: boolean, state: number) { path = [base, ...path]; let url = generatePath(path); if (usePath) { url = '#' + url; } + state++; if (isPop) { history.back(); - history.replaceState(null, null, url); + history.replaceState(state, null, url); } else { - history.pushState(null, null, url); + history.pushState(state, null, url); } + return state; } export function readPath(loc: Location, base: string, useHash: boolean): string[] | null { diff --git a/packages/core/src/components/router/router.tsx b/packages/core/src/components/router/router.tsx index 1f1de328cd..2929316bca 100644 --- a/packages/core/src/components/router/router.tsx +++ b/packages/core/src/components/router/router.tsx @@ -10,6 +10,7 @@ export class Router { private routes: RouterEntries; private busy = false; + private state = 0; @Prop({ context: 'config' }) config: Config; @Prop({ context: 'dom' }) dom: DomController; @@ -30,8 +31,12 @@ export class Router { }); } - @Listen('window:hashchange') + @Listen('window:popstate') protected onURLHashChanged() { + if (window.history.state === null) { + this.state++; + window.history.replaceState(this.state, document.title, document.location.href); + } if (!this.busy) { console.debug('[OUT] hash changed -> write nav state'); this.writeNavStateRoot(); @@ -43,35 +48,34 @@ export class Router { if (this.busy) { return; } - console.debug('[IN] nav changed -> update URL'); const { stack, pivot } = this.readNavState(); - const { path, routes} = matchPath(stack, this.routes); + const { path, routes } = matchPath(stack, this.routes); if (pivot) { // readNavState() found a pivot that is not initialized console.debug('[IN] pivot uninitialized -> write partial nav state'); - this.writeNavState(pivot, [], routes); + this.writeNavState(pivot, [], routes, 0); } const isPop = ev.detail.isPop === true; this.writePath(path, isPop); } - private writeNavStateRoot(): Promise { const node = document.querySelector('ion-app') as HTMLElement; const currentPath = this.readPath(); + const direction = window.history.state >= this.state ? 1 : -1; if (currentPath) { - return this.writeNavState(node, currentPath, this.routes); + return this.writeNavState(node, currentPath, this.routes, direction); } return Promise.resolve(); } - private writeNavState(node: any, path: string[], routes: RouterEntries): Promise { + private writeNavState(node: any, path: string[], routes: RouterEntries, direction: number): Promise { const chain = matchRouteChain(path, routes); this.busy = true; - return writeNavState(node, chain) + return writeNavState(node, chain, 0, direction) .catch(err => console.error(err)) .then(() => this.busy = false); } @@ -82,7 +86,7 @@ export class Router { } private writePath(path: string[], isPop: boolean) { - writePath(window.history, this.base, this.useHash, path, isPop); + this.state = writePath(window.history, this.base, this.useHash, path, isPop, this.state); } private readPath(): string[] | null { diff --git a/packages/core/src/components/router/test/basic/index.html b/packages/core/src/components/router/test/basic/index.html index 2633b7a9e0..eb68becf89 100644 --- a/packages/core/src/components/router/test/basic/index.html +++ b/packages/core/src/components/router/test/basic/index.html @@ -14,12 +14,17 @@ + + + - + + + @@ -43,7 +48,7 @@ title="Messages" icon="chatboxes" name="tab-four"> - INLINE CONTENT + diff --git a/packages/core/src/components/tab/tab.tsx b/packages/core/src/components/tab/tab.tsx index 1ee4f0f7d0..0ecf1c60ee 100644 --- a/packages/core/src/components/tab/tab.tsx +++ b/packages/core/src/components/tab/tab.tsx @@ -8,7 +8,7 @@ import { FrameworkDelegate } from '../..'; }) export class Tab { - // private loaded = false; + private loaded = false; @Element() el: HTMLElement; @State() init = false; @@ -81,36 +81,12 @@ export class Tab { @Method() setActive(active: boolean): Promise { this.active = active; - if (active) { - const nav = getNavAsChildIfExists(this.el); - if (nav) { - // the tab's nav has been initialized externally - - return ensureExternalRounterController().then((externalRouterController) => { - if (externalRouterController.getExternalNavPromise()) { - return (externalRouterController.getExternalNavPromise() as Promise).then(() => { - externalRouterController.clearExternalNavPromise(); - }); - } - - // the tab's nav has not been initialized externally, so - // check if we need to initiailize it - return (nav as any).componentOnReady().then(() => { - return nav.onAllTransitionsComplete(); - }).then(() => { - if (!nav.getViews().length && !nav.isTransitioning() && !nav.initialized) { - return nav.setRoot(nav.root); - } - return Promise.resolve(); - }); - }); - } - } - - /*if (this.loaded || !active) { + if (!active) { return Promise.resolve(); } - + if (this.loaded) { + return this.configChildgNav(); + } this.loaded = true; let promise: Promise; @@ -122,9 +98,7 @@ export class Tab { } else { promise = Promise.resolve(); } - return promise.then(() => this.fireChildren()); - */ - return Promise.resolve(); + return promise.then(() => this.configChildgNav()); } @Method() @@ -138,16 +112,32 @@ export class Tab { return null; } - /*private fireChildren() { + private configChildgNav(): Promise { const nav = getNavAsChildIfExists(this.el); - if (nav && nav.getViews().length === 0 && nav.root) { - // we need to initialize - return nav.setRoot(nav.root); + if (nav) { + // the tab's nav has been initialized externally + return ensureExternalRounterController().then((externalRouterController) => { + const externalNavPromise = externalRouterController.getExternalNavPromise(); + if (externalNavPromise) { + return externalNavPromise.then(() => { + externalRouterController.clearExternalNavPromise(); + }); + } + + // the tab's nav has not been initialized externally, so + // check if we need to initiailize it + return nav.componentOnReady() + .then(() => nav.onAllTransitionsComplete()) + .then(() => { + if (nav.getViews().length === 0 && !nav.isTransitioning() && !nav.initialized) { + return nav.setRoot(nav.root); + } + return Promise.resolve(); + }); + }); } - // it's already been initialized if it exists return Promise.resolve(); } - */ hostData() { const visible = this.active && this.selected; @@ -166,7 +156,7 @@ export class Tab { } } -/*function attachViewToDom(container: HTMLElement, cmp: string): Promise { +function attachViewToDom(container: HTMLElement, cmp: string): Promise { const el = document.createElement(cmp) as HTMLStencilElement; container.appendChild(el); if (el.componentOnReady) { @@ -174,4 +164,3 @@ export class Tab { } return Promise.resolve(); } -*/ diff --git a/packages/core/src/components/tabs/tabs.tsx b/packages/core/src/components/tabs/tabs.tsx index bbab8341c1..72ed8d1cec 100644 --- a/packages/core/src/components/tabs/tabs.tsx +++ b/packages/core/src/components/tabs/tabs.tsx @@ -160,12 +160,12 @@ export class Tabs implements NavOutlet { } @Method() - setRouteId(id: any, _: any = {}): Promise { - if (this.selectedTab === id) { - return Promise.resolve(); + setRouteId(id: any): Promise { + if (this.selectedTab && this.selectedTab.getRouteId() === id) { + return Promise.resolve(false); } const tab = this.tabs.find(t => id === t.getRouteId()); - return this.select(tab); + return this.select(tab).then(() => true); } @@ -185,13 +185,13 @@ export class Tabs implements NavOutlet { private initTabs() { const tabs = this.tabs = Array.from(this.el.querySelectorAll('ion-tab')); - const tabPromises: Promise[] = []; - for (const tab of tabs) { + const tabPromises = tabs.map(tab => { const id = `t-${this.tabsId}-${++this.ids}`; tab.btnId = 'tab-' + id; tab.id = 'tabpanel-' + id; - tabPromises.push((tab as any).componentOnReady()); - } + return tab.componentOnReady(); + }); + return Promise.all(tabPromises); }