mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-26 16:21:55 +08:00
fix(router): fix flickering
This commit is contained in:
@ -3,10 +3,10 @@ import { NavControllerBase } from './nav';
|
|||||||
import { Transition } from './transition';
|
import { Transition } from './transition';
|
||||||
|
|
||||||
|
|
||||||
export type Page2 = string | HTMLElement | ViewController;
|
export type NavParams = {[key: string]: any};
|
||||||
|
|
||||||
export interface PageMeta {
|
export interface PageMeta {
|
||||||
page: Page2;
|
page: string | HTMLElement | ViewController;
|
||||||
params?: any;
|
params?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { Component, Element, Event, EventEmitter, Method, Prop, Watch } from '@stencil/core';
|
import { Build, Component, Element, Event, EventEmitter, Method, Prop, Watch } from '@stencil/core';
|
||||||
import {
|
import {
|
||||||
DIRECTION_BACK,
|
DIRECTION_BACK,
|
||||||
DIRECTION_FORWARD,
|
DIRECTION_FORWARD,
|
||||||
INIT_ZINDEX,
|
INIT_ZINDEX,
|
||||||
NavOptions,
|
NavOptions,
|
||||||
|
NavParams,
|
||||||
NavResult,
|
NavResult,
|
||||||
STATE_ATTACHED,
|
STATE_ATTACHED,
|
||||||
STATE_DESTROYED,
|
STATE_DESTROYED,
|
||||||
@ -66,7 +67,7 @@ export class NavControllerBase implements NavOutlet {
|
|||||||
const useRouter = !!document.querySelector('ion-router');
|
const useRouter = !!document.querySelector('ion-router');
|
||||||
if (!useRouter) {
|
if (!useRouter) {
|
||||||
this.setRoot(this.root);
|
this.setRoot(this.root);
|
||||||
} else {
|
} else if (Build.isDev) {
|
||||||
console.warn('<ion-nav> does not support a root attribute when using ion-router.');
|
console.warn('<ion-nav> does not support a root attribute when using ion-router.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,7 +91,7 @@ export class NavControllerBase implements NavOutlet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Method()
|
@Method()
|
||||||
push(page: any, params?: any, opts?: NavOptions, done?: TransitionDoneFn): Promise<any> {
|
push(page: any, params?: NavParams, opts?: NavOptions, done?: TransitionDoneFn): Promise<any> {
|
||||||
return this._queueTrns({
|
return this._queueTrns({
|
||||||
insertStart: -1,
|
insertStart: -1,
|
||||||
insertViews: [{ page: page, params: params }],
|
insertViews: [{ page: page, params: params }],
|
||||||
@ -99,7 +100,7 @@ export class NavControllerBase implements NavOutlet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Method()
|
@Method()
|
||||||
insert(insertIndex: number, page: any, params?: any, opts?: NavOptions, done?: TransitionDoneFn): Promise<any> {
|
insert(insertIndex: number, page: any, params?: NavParams, opts?: NavOptions, done?: TransitionDoneFn): Promise<any> {
|
||||||
return this._queueTrns({
|
return this._queueTrns({
|
||||||
insertStart: insertIndex,
|
insertStart: insertIndex,
|
||||||
insertViews: [{ page: page, params: params }],
|
insertViews: [{ page: page, params: params }],
|
||||||
|
@ -31,7 +31,10 @@ boolean
|
|||||||
|
|
||||||
## Methods
|
## Methods
|
||||||
|
|
||||||
#### pushURL()
|
#### navChanged()
|
||||||
|
|
||||||
|
|
||||||
|
#### push()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Component, Element, Listen, Method, Prop } from '@stencil/core';
|
import { Build, Component, Element, Listen, Method, Prop } from '@stencil/core';
|
||||||
import { Config, DomController } from '../../index';
|
import { Config, DomController } from '../../index';
|
||||||
import { flattenRouterTree, readRoutes } from './utils/parser';
|
import { flattenRouterTree, readRoutes } from './utils/parser';
|
||||||
import { readNavState, writeNavState } from './utils/dom';
|
import { readNavState, writeNavState } from './utils/dom';
|
||||||
import { chainToPath, parsePath, readPath, writePath } from './utils/path';
|
import { chainToPath, generatePath, parsePath, readPath, writePath } from './utils/path';
|
||||||
import { RouteChain } from './utils/interfaces';
|
import { RouteChain } from './utils/interfaces';
|
||||||
import { routerIDsToChain, routerPathToChain } from './utils/matching';
|
import { routerIDsToChain, routerPathToChain } from './utils/matching';
|
||||||
|
|
||||||
@ -29,6 +29,16 @@ export class Router {
|
|||||||
const tree = readRoutes(this.el);
|
const tree = readRoutes(this.el);
|
||||||
this.routes = flattenRouterTree(tree);
|
this.routes = flattenRouterTree(tree);
|
||||||
|
|
||||||
|
if (Build.isDev) {
|
||||||
|
console.debug('%c[@ionic/core]', 'font-weight: bold', `ion-router registered ${this.routes.length} routes`);
|
||||||
|
for (const chain of this.routes) {
|
||||||
|
const path: string[] = [];
|
||||||
|
chain.forEach(r => path.push(...r.path));
|
||||||
|
const ids = chain.map(r => r.id);
|
||||||
|
console.debug(`%c ${generatePath(path)}`, 'font-weight: bold; padding-left: 20px', '=>\t', `(${ids.join(', ')})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// perform first write
|
// perform first write
|
||||||
this.dom.raf(() => {
|
this.dom.raf(() => {
|
||||||
console.debug('[OUT] page load -> write nav state');
|
console.debug('[OUT] page load -> write nav state');
|
||||||
@ -45,32 +55,32 @@ export class Router {
|
|||||||
this.writeNavStateRoot();
|
this.writeNavStateRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Listen('body:ionNavChanged')
|
@Method()
|
||||||
protected onNavChanged(ev: CustomEvent) {
|
navChanged(isPop: boolean) {
|
||||||
if (this.busy) {
|
if (!this.busy) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.debug('[IN] nav changed -> update URL');
|
console.debug('[IN] nav changed -> update URL');
|
||||||
const { ids, pivot } = this.readNavState();
|
const { ids, pivot } = this.readNavState();
|
||||||
const chain = routerIDsToChain(ids, this.routes);
|
const chain = routerIDsToChain(ids, this.routes);
|
||||||
if (chain) {
|
if (chain) {
|
||||||
|
const path = chainToPath(chain);
|
||||||
|
this.writePath(path, isPop);
|
||||||
|
|
||||||
if (chain.length > ids.length) {
|
if (chain.length > ids.length) {
|
||||||
// readNavState() found a pivot that is not initialized
|
// readNavState() found a pivot that is not initialized
|
||||||
console.debug('[IN] pivot uninitialized -> write partial nav state');
|
console.debug('[IN] pivot uninitialized -> write partial nav state');
|
||||||
this.writeNavState(pivot, chain.slice(ids.length), 0);
|
return this.writeNavState(pivot, chain.slice(ids.length), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPop = ev.detail.isPop === true;
|
|
||||||
const path = chainToPath(chain);
|
|
||||||
this.writePath(path, isPop);
|
|
||||||
} else {
|
} else {
|
||||||
console.warn('no matching URL for ', ids.map(i => i.id));
|
console.warn('no matching URL for ', ids.map(i => i.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Method()
|
@Method()
|
||||||
pushURL(url: string, isPop = false) {
|
push(url: string, backDirection = false) {
|
||||||
this.writePath(parsePath(url), isPop);
|
this.writePath(parsePath(url), backDirection);
|
||||||
return this.writeNavStateRoot();
|
return this.writeNavStateRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
import { RouterSegments, breadthFirstSearch } from '../utils/common';
|
|
||||||
|
|
||||||
describe('RouterSegments', () => {
|
|
||||||
it ('should initialize with empty array', () => {
|
|
||||||
const s = new RouterSegments([]);
|
|
||||||
expect(s.next()).toEqual('');
|
|
||||||
expect(s.next()).toEqual('');
|
|
||||||
expect(s.next()).toEqual('');
|
|
||||||
expect(s.next()).toEqual('');
|
|
||||||
expect(s.next()).toEqual('');
|
|
||||||
});
|
|
||||||
|
|
||||||
it ('should initialize with array', () => {
|
|
||||||
const s = new RouterSegments(['', 'path', 'to', 'destination']);
|
|
||||||
expect(s.next()).toEqual('');
|
|
||||||
expect(s.next()).toEqual('path');
|
|
||||||
expect(s.next()).toEqual('to');
|
|
||||||
expect(s.next()).toEqual('destination');
|
|
||||||
expect(s.next()).toEqual('');
|
|
||||||
expect(s.next()).toEqual('');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('breadthFirstSearch', () => {
|
|
||||||
it('should search in order', () => {
|
|
||||||
const n1 = { tagName: 'ION-TABS', children: [] as any };
|
|
||||||
const n2 = { tagName: 'DIV', children: [n1] };
|
|
||||||
const n3 = { tagName: 'ION-NAV', children: [n2] };
|
|
||||||
const n4 = { tagName: 'ION-TABS', children: [] as any };
|
|
||||||
const n5 = { tagName: 'DIV', children: [n4] };
|
|
||||||
const n6 = { tagName: 'DIV', children: [n5, n3] };
|
|
||||||
const n7 = { tagName: 'DIV', children: [] as any };
|
|
||||||
const n8 = { tagName: 'DIV', children: [n6] };
|
|
||||||
const n9 = { tagName: 'DIV', children: [n8, n7] };
|
|
||||||
|
|
||||||
expect(breadthFirstSearch(n9 as any)).toBe(n3);
|
|
||||||
expect(breadthFirstSearch(n8 as any)).toBe(n3);
|
|
||||||
expect(breadthFirstSearch(n7 as any)).toBe(null);
|
|
||||||
expect(breadthFirstSearch(n6 as any)).toBe(n3);
|
|
||||||
expect(breadthFirstSearch(n5 as any)).toBe(n4);
|
|
||||||
expect(breadthFirstSearch(n4 as any)).toBe(n4);
|
|
||||||
expect(breadthFirstSearch(n3 as any)).toBe(n3);
|
|
||||||
expect(breadthFirstSearch(n2 as any)).toBe(n1);
|
|
||||||
expect(breadthFirstSearch(n1 as any)).toBe(n1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
import { RouteChain } from '../utils/interfaces';
|
import { RouteChain } from '../utils/interfaces';
|
||||||
import { matchesIDs, matchesPath, mergeParams, routerPathToChain } from '../utils/matching';
|
import { RouterSegments, matchesIDs, matchesPath, mergeParams, routerPathToChain } from '../utils/matching';
|
||||||
import { parsePath } from '../utils/path';
|
import { parsePath } from '../utils/path';
|
||||||
|
|
||||||
const CHAIN_1: RouteChain = [
|
const CHAIN_1: RouteChain = [
|
||||||
@ -190,6 +190,28 @@ describe('mergeParams', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('RouterSegments', () => {
|
||||||
|
it ('should initialize with empty array', () => {
|
||||||
|
const s = new RouterSegments([]);
|
||||||
|
expect(s.next()).toEqual('');
|
||||||
|
expect(s.next()).toEqual('');
|
||||||
|
expect(s.next()).toEqual('');
|
||||||
|
expect(s.next()).toEqual('');
|
||||||
|
expect(s.next()).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it ('should initialize with array', () => {
|
||||||
|
const s = new RouterSegments(['', 'path', 'to', 'destination']);
|
||||||
|
expect(s.next()).toEqual('');
|
||||||
|
expect(s.next()).toEqual('path');
|
||||||
|
expect(s.next()).toEqual('to');
|
||||||
|
expect(s.next()).toEqual('destination');
|
||||||
|
expect(s.next()).toEqual('');
|
||||||
|
expect(s.next()).toEqual('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// describe('matchRoute', () => {
|
// describe('matchRoute', () => {
|
||||||
// it('should match simple route', () => {
|
// it('should match simple route', () => {
|
||||||
// const path = ['path', 'to', 'component'];
|
// const path = ['path', 'to', 'component'];
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
import { NavOutletElement } from './interfaces';
|
|
||||||
|
|
||||||
export class RouterSegments {
|
|
||||||
private path: string[];
|
|
||||||
constructor(path: string[]) {
|
|
||||||
this.path = path.slice();
|
|
||||||
}
|
|
||||||
|
|
||||||
next(): string {
|
|
||||||
if (this.path.length > 0) {
|
|
||||||
return this.path.shift() as string;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const navs = ['ION-NAV', 'ION-TABS'];
|
|
||||||
export function breadthFirstSearch(root: HTMLElement): NavOutletElement | null {
|
|
||||||
if (!root) {
|
|
||||||
console.error('search root is null');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// we do a Breadth-first search
|
|
||||||
// Breadth-first search (BFS) is an algorithm for traversing or searching tree
|
|
||||||
// or graph data structures.It starts at the tree root(or some arbitrary node of a graph,
|
|
||||||
// sometimes referred to as a 'search key'[1]) and explores the neighbor nodes
|
|
||||||
// first, before moving to the next level neighbours.
|
|
||||||
|
|
||||||
const queue = [root];
|
|
||||||
let node: HTMLElement | undefined;
|
|
||||||
while (node = queue.shift()) {
|
|
||||||
// visit node
|
|
||||||
if (navs.indexOf(node.tagName) >= 0) {
|
|
||||||
return node as NavOutletElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
// queue children
|
|
||||||
const children = node.children;
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
|
||||||
queue.push(children[i] as NavOutletElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
@ -1,12 +1,11 @@
|
|||||||
import { breadthFirstSearch } from './common';
|
import { NavOutlet, NavOutletElement, RouteChain, RouteID } from './interfaces';
|
||||||
import { NavOutlet, RouteChain, RouteID } from './interfaces';
|
|
||||||
|
|
||||||
export function writeNavState(root: HTMLElement, chain: RouteChain|null, index: number, direction: number): Promise<void> {
|
export function writeNavState(root: HTMLElement, chain: RouteChain|null, index: number, direction: number): Promise<void> {
|
||||||
if (!chain || index >= chain.length) {
|
if (!chain || index >= chain.length) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
const route = chain[index];
|
const route = chain[index];
|
||||||
const node = breadthFirstSearch(root);
|
const node = searchNavNode(root);
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
@ -32,7 +31,7 @@ export function readNavState(node: HTMLElement) {
|
|||||||
const ids: RouteID[] = [];
|
const ids: RouteID[] = [];
|
||||||
let pivot: NavOutlet|null;
|
let pivot: NavOutlet|null;
|
||||||
while (true) {
|
while (true) {
|
||||||
pivot = breadthFirstSearch(node);
|
pivot = searchNavNode(node);
|
||||||
if (pivot) {
|
if (pivot) {
|
||||||
const id = pivot.getRouteId();
|
const id = pivot.getRouteId();
|
||||||
if (id) {
|
if (id) {
|
||||||
@ -47,3 +46,12 @@ export function readNavState(node: HTMLElement) {
|
|||||||
}
|
}
|
||||||
return {ids, pivot};
|
return {ids, pivot};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QUERY = 'ion-nav,ion-tabs';
|
||||||
|
|
||||||
|
function searchNavNode(root: HTMLElement): NavOutletElement {
|
||||||
|
if (root.matches(QUERY)) {
|
||||||
|
return root as NavOutletElement;
|
||||||
|
}
|
||||||
|
return root.querySelector(QUERY);
|
||||||
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { RouterSegments } from './common';
|
|
||||||
import { RouteChain, RouteID } from './interfaces';
|
import { RouteChain, RouteID } from './interfaces';
|
||||||
|
|
||||||
export function matchesIDs(ids: string[], chain: RouteChain): number {
|
export function matchesIDs(ids: string[], chain: RouteChain): number {
|
||||||
@ -12,7 +11,6 @@ export function matchesIDs(ids: string[], chain: RouteChain): number {
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function matchesPath(path: string[], chain: RouteChain): RouteChain | null {
|
export function matchesPath(path: string[], chain: RouteChain): RouteChain | null {
|
||||||
const segments = new RouterSegments(path);
|
const segments = new RouterSegments(path);
|
||||||
let matchesDefault = false;
|
let matchesDefault = false;
|
||||||
@ -107,3 +105,18 @@ export function routerPathToChain(path: string[], chains: RouteChain[]): RouteCh
|
|||||||
}
|
}
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class RouterSegments {
|
||||||
|
private path: string[];
|
||||||
|
constructor(path: string[]) {
|
||||||
|
this.path = path.slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
next(): string {
|
||||||
|
if (this.path.length > 0) {
|
||||||
|
return this.path.shift() as string;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -5,12 +5,18 @@ import { parsePath } from './path';
|
|||||||
export function readRoutes(root: Element): RouteTree {
|
export function readRoutes(root: Element): RouteTree {
|
||||||
return (Array.from(root.children) as HTMLIonRouteElement[])
|
return (Array.from(root.children) as HTMLIonRouteElement[])
|
||||||
.filter(el => el.tagName === 'ION-ROUTE')
|
.filter(el => el.tagName === 'ION-ROUTE')
|
||||||
.map(el => ({
|
.map(el => {
|
||||||
path: parsePath(readProp(el, 'path')),
|
const path = parsePath(readProp(el, 'path'));
|
||||||
|
if (path.includes(':id')) {
|
||||||
|
console.warn('Using ":id" is not recommended in `ion-route`, it causes conflicts in the DOM');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
path: path,
|
||||||
id: readProp(el, 'component').toLowerCase(),
|
id: readProp(el, 'component').toLowerCase(),
|
||||||
params: el.params,
|
params: el.params,
|
||||||
children: readRoutes(el)
|
children: readRoutes(el)
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readProp(el: HTMLElement, prop: string): string|undefined {
|
export function readProp(el: HTMLElement, prop: string): string|undefined {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
|
import { Build, Component, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
tag: 'ion-tab',
|
tag: 'ion-tab',
|
||||||
@ -83,6 +83,16 @@ export class Tab {
|
|||||||
*/
|
*/
|
||||||
@Event() ionSelect: EventEmitter<void>;
|
@Event() ionSelect: EventEmitter<void>;
|
||||||
|
|
||||||
|
componentWillLoad() {
|
||||||
|
if (Build.isDev) {
|
||||||
|
if (this.component && this.el.childElementCount > 0) {
|
||||||
|
console.error('You can not use a lazy-loaded component in a tab and inlineed content at the same time.' +
|
||||||
|
`- Remove the component attribute in: <ion-tab component="${this.component}">` +
|
||||||
|
` or` +
|
||||||
|
`- Remove the embeded content inside the ion-tab: <ion-tab></ion-tab>`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@Method()
|
@Method()
|
||||||
getTabId(): string|null {
|
getTabId(): string|null {
|
||||||
if (this.name) {
|
if (this.name) {
|
||||||
@ -95,11 +105,13 @@ export class Tab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Method()
|
@Method()
|
||||||
setActive(): Promise<HTMLIonTabElement> {
|
setActive(): Promise<void> {
|
||||||
return this.prepareLazyLoaded().then(() => this.showTab());
|
return this.prepareLazyLoaded().then(() => {
|
||||||
|
this.active = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private prepareLazyLoaded(): Promise<any> {
|
private prepareLazyLoaded(): Promise<HTMLElement|void> {
|
||||||
if (!this.loaded && this.component) {
|
if (!this.loaded && this.component) {
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
return attachViewToDom(this.el, this.component);
|
return attachViewToDom(this.el, this.component);
|
||||||
@ -107,11 +119,6 @@ export class Tab {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
private showTab(): Promise<HTMLIonTabElement> {
|
|
||||||
this.active = true;
|
|
||||||
return Promise.resolve(this.el);
|
|
||||||
}
|
|
||||||
|
|
||||||
hostData() {
|
hostData() {
|
||||||
return {
|
return {
|
||||||
'aria-labelledby': this.btnId,
|
'aria-labelledby': this.btnId,
|
||||||
@ -125,12 +132,12 @@ export class Tab {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachViewToDom(container: HTMLElement, cmp: string): Promise<any> {
|
function attachViewToDom(container: HTMLElement, cmp: string): Promise<HTMLElement> {
|
||||||
const el = document.createElement(cmp) as HTMLStencilElement;
|
const el = document.createElement(cmp) as HTMLStencilElement;
|
||||||
el.classList.add('ion-page');
|
el.classList.add('ion-page');
|
||||||
container.appendChild(el);
|
container.appendChild(el);
|
||||||
if (el.componentOnReady) {
|
if (el.componentOnReady) {
|
||||||
return el.componentOnReady();
|
return el.componentOnReady();
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve(el);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { 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 } from '../router/utils/interfaces';
|
import { RouteID } from '../router/utils/interfaces';
|
||||||
|
|
||||||
@ -11,8 +11,8 @@ export class Tabs implements NavOutlet {
|
|||||||
|
|
||||||
private ids = -1;
|
private ids = -1;
|
||||||
private transitioning = false;
|
private transitioning = false;
|
||||||
private routingView: HTMLIonTabElement;
|
|
||||||
private tabsId: number = (++tabIds);
|
private tabsId: number = (++tabIds);
|
||||||
|
private leavingTab: HTMLIonTabElement | undefined;
|
||||||
|
|
||||||
@Element() el: HTMLElement;
|
@Element() el: HTMLElement;
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ export class Tabs implements NavOutlet {
|
|||||||
|
|
||||||
componentDidUnload() {
|
componentDidUnload() {
|
||||||
this.tabs.length = 0;
|
this.tabs.length = 0;
|
||||||
this.selectedTab = this.routingView = undefined;
|
this.selectedTab = this.leavingTab = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Listen('ionTabbarClick')
|
@Listen('ionTabbarClick')
|
||||||
@ -95,8 +95,22 @@ export class Tabs implements NavOutlet {
|
|||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
select(tabOrIndex: number | HTMLIonTabElement): Promise<boolean> {
|
select(tabOrIndex: number | HTMLIonTabElement): Promise<boolean> {
|
||||||
return this.setActive(tabOrIndex)
|
const selectedTab = this.getTab(tabOrIndex);
|
||||||
.then(selectedTab => this.tabSwitch(selectedTab));
|
if (!this.shouldSwitch(selectedTab)) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
return this.setActive(selectedTab)
|
||||||
|
.then(() => this.notifyRouter())
|
||||||
|
.then(() => this.tabSwitch());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
setRouteId(id: string): Promise<boolean> {
|
||||||
|
const selectedTab = this.getTab(id);
|
||||||
|
if (!this.shouldSwitch(selectedTab)) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
return this.setActive(selectedTab).then(() => true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Method()
|
@Method()
|
||||||
@ -118,21 +132,10 @@ export class Tabs implements NavOutlet {
|
|||||||
return this.selectedTab;
|
return this.selectedTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Method()
|
|
||||||
setRouteId(id: string): Promise<boolean> {
|
|
||||||
return this.setActive(id).then(tab => {
|
|
||||||
if (tab) {
|
|
||||||
this.routingView = tab;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Method()
|
@Method()
|
||||||
markVisible(): Promise<void> {
|
markVisible(): Promise<void> {
|
||||||
this.tabSwitch(this.routingView);
|
this.tabSwitch();
|
||||||
this.routingView = null;
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,10 +145,9 @@ export class Tabs implements NavOutlet {
|
|||||||
return id ? {id} : null;
|
return id ? {id} : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Method()
|
@Method()
|
||||||
getContainerEl(): HTMLElement {
|
getContainerEl(): HTMLElement {
|
||||||
return this.routingView || this.selectedTab;
|
return this.selectedTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
private initTabs() {
|
private initTabs() {
|
||||||
@ -162,6 +164,13 @@ export class Tabs implements NavOutlet {
|
|||||||
|
|
||||||
private initSelect(): Promise<void> {
|
private initSelect(): Promise<void> {
|
||||||
if (document.querySelector('ion-router')) {
|
if (document.querySelector('ion-router')) {
|
||||||
|
if (Build.isDev) {
|
||||||
|
const selectedTab = this.tabs.find(t => t.selected);
|
||||||
|
if (selectedTab) {
|
||||||
|
console.warn('When using a router (ion-router) <ion-tab selected="true"> makes no difference' +
|
||||||
|
'Define routes properly the define which tab is selected');
|
||||||
|
}
|
||||||
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
// find pre-selected tabs
|
// find pre-selected tabs
|
||||||
@ -191,13 +200,13 @@ export class Tabs implements NavOutlet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setActive(tabOrIndex: string | number | HTMLIonTabElement): Promise<HTMLIonTabElement|null> {
|
private setActive(selectedTab: HTMLIonTabElement): Promise<void> {
|
||||||
if (this.transitioning) {
|
if (this.transitioning) {
|
||||||
return Promise.resolve(null);
|
return Promise.reject('transitioning already happening');
|
||||||
}
|
}
|
||||||
const selectedTab = this.getTab(tabOrIndex);
|
|
||||||
if (!selectedTab || selectedTab === this.selectedTab) {
|
if (!selectedTab) {
|
||||||
return Promise.resolve(null);
|
return Promise.reject('no tab is selected');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset rest of tabs
|
// Reset rest of tabs
|
||||||
@ -208,21 +217,26 @@ export class Tabs implements NavOutlet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.transitioning = true;
|
this.transitioning = true;
|
||||||
|
this.leavingTab = this.selectedTab;
|
||||||
|
this.selectedTab = selectedTab;
|
||||||
return selectedTab.setActive();
|
return selectedTab.setActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
private tabSwitch(selectedTab: HTMLIonTabElement | null): boolean {
|
private tabSwitch(): boolean {
|
||||||
|
const selectedTab = this.selectedTab;
|
||||||
|
const leavingTab = this.leavingTab;
|
||||||
|
|
||||||
|
this.leavingTab = undefined;
|
||||||
this.transitioning = false;
|
this.transitioning = false;
|
||||||
if (!selectedTab) {
|
if (!selectedTab) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const leavingTab = this.selectedTab;
|
|
||||||
selectedTab.selected = true;
|
selectedTab.selected = true;
|
||||||
if (leavingTab !== selectedTab) {
|
if (leavingTab !== selectedTab) {
|
||||||
if (leavingTab) {
|
if (leavingTab) {
|
||||||
leavingTab.active = false;
|
leavingTab.active = false;
|
||||||
}
|
}
|
||||||
this.selectedTab = selectedTab;
|
|
||||||
this.ionChange.emit(selectedTab);
|
this.ionChange.emit(selectedTab);
|
||||||
this.ionNavChanged.emit({isPop: false});
|
this.ionNavChanged.emit({isPop: false});
|
||||||
return true;
|
return true;
|
||||||
@ -230,6 +244,19 @@ export class Tabs implements NavOutlet {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private notifyRouter() {
|
||||||
|
const router = document.querySelector('ion-router');
|
||||||
|
if (router) {
|
||||||
|
return router.navChanged(false);
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldSwitch(selectedTab: HTMLIonTabElement) {
|
||||||
|
const leavingTab = this.selectedTab;
|
||||||
|
return selectedTab && selectedTab !== leavingTab && !this.transitioning;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const dom = [
|
const dom = [
|
||||||
<div class='tabs-inner'>
|
<div class='tabs-inner'>
|
||||||
|
@ -63,11 +63,11 @@ export function getClassMap(classes: string | undefined): CssClassMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function openURL(url: string, ev: Event, isPop = false) {
|
export function openURL(url: string, ev: Event, isPop = false) {
|
||||||
if (url && 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.pushURL(url, isPop));
|
return router.componentOnReady().then(() => router.push(url, isPop));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
Reference in New Issue
Block a user