From c03afabc0fd435690040a96a109078552847a309 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Wed, 21 Mar 2018 15:48:49 +0100 Subject: [PATCH] feat(ion-router-outlet): adds router-outlet --- core/src/components.d.ts | 33 +++++ core/src/components/app/app.scss | 29 ++++ core/src/components/nav/nav.scss | 27 ---- core/src/components/router-outlet/readme.md | 59 +++++++++ .../components/router-outlet/route-outlet.tsx | 125 ++++++++++++++++++ .../router-outlet/test/basic/index.html | 78 +++++++++++ 6 files changed, 324 insertions(+), 27 deletions(-) create mode 100644 core/src/components/router-outlet/readme.md create mode 100644 core/src/components/router-outlet/route-outlet.tsx create mode 100644 core/src/components/router-outlet/test/basic/index.html diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 412f2def43..2787a2bb6b 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -43,6 +43,7 @@ import { Side, } from './utils/helpers'; import { + AnimationBuilder as AnimationBuilder2, FrameworkDelegate as FrameworkDelegate2, } from '.'; import { @@ -2623,6 +2624,38 @@ declare global { } +import { + RouterOutlet as IonRouterOutlet +} from './components/router-outlet/route-outlet'; + +declare global { + interface HTMLIonRouterOutletElement extends IonRouterOutlet, HTMLStencilElement { + } + var HTMLIonRouterOutletElement: { + prototype: HTMLIonRouterOutletElement; + new (): HTMLIonRouterOutletElement; + }; + interface HTMLElementTagNameMap { + "ion-router-outlet": HTMLIonRouterOutletElement; + } + interface ElementTagNameMap { + "ion-router-outlet": HTMLIonRouterOutletElement; + } + namespace JSX { + interface IntrinsicElements { + "ion-router-outlet": JSXElements.IonRouterOutletAttributes; + } + } + namespace JSXElements { + export interface IonRouterOutletAttributes extends HTMLAttributes { + animated?: boolean; + animationBuilder?: AnimationBuilder; + delegate?: FrameworkDelegate; + } + } +} + + import { Router as IonRouter } from './components/router/router'; diff --git a/core/src/components/app/app.scss b/core/src/components/app/app.scss index fddb49de36..c0650bf89a 100644 --- a/core/src/components/app/app.scss +++ b/core/src/components/app/app.scss @@ -170,6 +170,35 @@ ion-app, contain: layout size style; } +.hide-page { + opacity: 0; +} + +// TODO: move to somewhere else + +$navigation-ios-transition-background: #000 !default; + +.nav-decor { + display: none; +} + +.show-decor > .nav-decor { + @include position(0, null, null, 0); + + // when ios pages transition, the leaving page grays out + // this is the black square behind all pages so they gray out + position: absolute; + z-index: 0; + display: block; + + width: 100%; + height: 100%; + + background: $navigation-ios-transition-background; + + pointer-events: none; +} + // Misc // -------------------------------------------------- diff --git a/core/src/components/nav/nav.scss b/core/src/components/nav/nav.scss index bf4672e39f..5b052883f5 100644 --- a/core/src/components/nav/nav.scss +++ b/core/src/components/nav/nav.scss @@ -1,7 +1,5 @@ @import "../../themes/util"; -$navigation-ios-transition-background: #000 !default; - ion-nav { @include position(0); @@ -14,28 +12,3 @@ ion-nav { contain: layout size style; } - -.hide-page { - opacity: 0; -} - -.nav-decor { - display: none; -} - -.show-decor > .nav-decor { - @include position(0, null, null, 0); - - // when ios pages transition, the leaving page grays out - // this is the black square behind all pages so they gray out - position: absolute; - z-index: 0; - display: block; - - width: 100%; - height: 100%; - - background: $navigation-ios-transition-background; - - pointer-events: none; -} diff --git a/core/src/components/router-outlet/readme.md b/core/src/components/router-outlet/readme.md new file mode 100644 index 0000000000..db9e481227 --- /dev/null +++ b/core/src/components/router-outlet/readme.md @@ -0,0 +1,59 @@ +# ion-router-outlet + + + + + + +## Properties + +#### animated + +boolean + + +#### animationBuilder + + + + +#### delegate + + + + +## Attributes + +#### animated + +boolean + + +#### animation-builder + + + + +#### delegate + + + + +## Methods + +#### commit() + + +#### getRouteId() + + +#### setRoot() + + +#### setRouteId() + + + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/core/src/components/router-outlet/route-outlet.tsx b/core/src/components/router-outlet/route-outlet.tsx new file mode 100644 index 0000000000..99885e763d --- /dev/null +++ b/core/src/components/router-outlet/route-outlet.tsx @@ -0,0 +1,125 @@ +import { Component, Element, Method, Prop } from '@stencil/core'; +import { transition } from '../../utils'; +import { NavDirection } from '../nav/nav-util'; +import { AnimationBuilder, Config, FrameworkDelegate, NavOutlet } from '../..'; +import { attachComponent, detachComponent } from '../../utils/overlays'; + +import iosTransitionAnimation from '../nav/animations/ios.transition'; +import mdTransitionAnimation from '../nav/animations/md.transition'; +import { RouteID, RouteWrite } from '../router/utils/interfaces'; + +@Component({ + tag: 'ion-router-outlet' +}) +export class RouterOutlet implements NavOutlet { + + private isTransitioning = false; + private activeEl: HTMLElement = undefined; + + mode: string; + + @Element() el: HTMLElement; + + @Prop({context: 'config'}) config: Config; + @Prop({connect: 'ion-animation-controller'}) animationCtrl: HTMLIonAnimationControllerElement; + + @Prop() animated = true; + @Prop() animationBuilder: AnimationBuilder; + @Prop() delegate: FrameworkDelegate; + + componentDidUnload() { + this.activeEl = undefined; + } + + @Method() + async setRoot(component: HTMLElement|string, params?: {[key: string]: any}, opts?: RouterOutletOptions): Promise { + if (this.isTransitioning) { + return false; + } + // attach entering view to DOM + const enteringEl = await attachComponent(this.delegate, this.el, component, NAV_CLASSES, params); + const leavingEl = this.activeEl; + + // commit animation + await this.commit(enteringEl, opts); + + // remove leaving view + detachComponent(this.delegate, leavingEl); + + return true; + } + + @Method() + async commit(enteringEl: HTMLElement, opts?: RouterOutletOptions): Promise { + // isTransitioning acts as a lock to prevent reentering + if (this.isTransitioning || this.activeEl === enteringEl) { + return false; + } + this.isTransitioning = true; + + opts = opts || {}; + + await transition({ + animationBuilder: this.getAnimationBuilder(opts), + direction: opts.direction, + duration: opts.duration, + easing: opts.easing, + + animationCtrl: this.animationCtrl, + enteringEl: enteringEl, + leavingEl: this.activeEl, + baseEl: this.el, + }); + this.activeEl = enteringEl; + this.isTransitioning = false; + return true; + } + + @Method() + async setRouteId(id: string, data: any, direction: number): Promise { + const changed = await this.setRoot(id, data, { + duration: direction === 0 ? 0 : undefined, + direction: direction === -1 ? NavDirection.back : NavDirection.forward, + }); + return { + changed, + element: this.activeEl + }; + } + + @Method() + getRouteId(): RouteID|undefined { + const active = this.activeEl; + return active ? { + id: active.tagName, + element: active, + } : undefined; + } + + private getAnimationBuilder(opts: RouterOutletOptions) { + if (opts.duration === 0 || this.animated === false || this.activeEl === undefined) { + return undefined; + } + const mode = opts.mode || this.config.get('pageTransition', this.mode); + return opts.animationBuilder + || this.animationBuilder + || mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation; + } + + render() { + return [ + this.mode === 'ios' &&