feat(vue): support ion-vue-router (#17821)

This commit is contained in:
Michael Tintiuc
2019-03-22 18:04:46 +02:00
committed by Mike Hartington
parent ee7167512f
commit 71e5994c94
8 changed files with 182 additions and 212 deletions

View File

@ -1,16 +0,0 @@
<template>
<div @click="catchIonicGoBack">
<router-view :name="name"/>
</div>
</template>
<script lang="ts">
import { Prop } from 'vue-property-decorator';
import Component, { mixins } from 'vue-class-component';
import CatchIonicGoBack from '../mixins/catch-ionic-go-back';
@Component
export default class IonVueRouter extends mixins(CatchIonicGoBack) {
@Prop({ default: 'default'}) name!: string;
}
</script>

View File

@ -0,0 +1,149 @@
import Vue, { CreateElement, RenderContext, VNodeData } from 'vue';
type TransitionDone = () => void;
interface Props {
name: string;
animated: boolean;
}
// Component entering the view
let enteringEl: HTMLElement;
export default {
name: 'IonVueRouter',
functional: true,
props: {
// Router view name
name: { default: 'default', type: String },
// Disable transitions
animated: { default: true, type: Boolean },
},
render(h: CreateElement, { parent, props, data, children }: RenderContext) {
if (!parent.$router) {
throw new Error('IonTabs requires an instance of either VueRouter or IonicVueRouter');
}
const ionRouterOutletData: VNodeData = {
...data,
ref: 'ionRouterOutlet',
on: { click: (event: Event) => catchIonicGoBack(parent, event) },
};
const routerViewData: VNodeData = { props: { name: props.name } };
const transitionData: VNodeData = {
props: { css: false, mode: 'in-out' },
on: {
leave: (el: HTMLElement, done: TransitionDone) => {
leave(parent, props as Props, el, done);
},
beforeEnter,
enter,
afterEnter,
beforeLeave,
afterLeave,
enterCancelled,
leaveCancelled,
}
};
return h('ion-router-outlet', ionRouterOutletData, [
h('transition', transitionData, [
h('router-view', routerViewData, children)
])
]);
}
};
function catchIonicGoBack(parent: Vue, event: Event): void {
if (!event.target) return;
// We only care for the event coming from Ionic's back button
const backButton = (event.target as HTMLElement).closest('ion-back-button') as HTMLIonBackButtonElement;
if (!backButton) return;
const $router = parent.$router;
let defaultHref: string;
// Explicitly override router direction to always trigger a back transition
$router.directionOverride = -1;
// If we can go back - do so
if ($router.canGoBack()) {
event.preventDefault();
$router.back();
return;
}
// If there's a default fallback - use it
defaultHref = backButton.defaultHref as string;
if (undefined !== defaultHref) {
event.preventDefault();
$router.push(defaultHref);
}
}
// Transition when we leave the route
function leave(parent: Vue, props: Props, el: HTMLElement, done: TransitionDone) {
const promise = transition(parent, props, el);
// Skip any transition if we don't get back a Promise
if (!promise) {
done();
return;
}
// Perform navigation once the transition was finished
parent.$router.transition = new Promise(resolve => {
promise.then(() => {
resolve();
done();
}).catch(console.error);
});
}
// Trigger the ionic/core transitions
function transition(parent: Vue, props: Props, leavingEl: HTMLElement) {
const ionRouterOutlet = parent.$refs.ionRouterOutlet as HTMLIonRouterOutletElement;
// The Ionic framework didn't load - skip animations
if (typeof ionRouterOutlet.componentOnReady === 'undefined') {
return;
}
// Skip animations if there's no component to navigate to
// or the current and the "to-be-rendered" components are the same
if (!enteringEl || enteringEl === leavingEl) {
return;
}
// Add the proper Ionic classes, important for smooth transitions
enteringEl.classList.add('ion-page', 'ion-page-invisible');
// Commit to the transition as soon as the Ionic Router Outlet is ready
return ionRouterOutlet.componentOnReady().then((el: HTMLIonRouterOutletElement) => {
return el.commit(enteringEl, leavingEl, {
deepWait: true,
duration: !props.animated ? 0 : undefined,
direction: parent.$router.direction === 1 ? 'forward' : 'back',
showGoBack: parent.$router.canGoBack(),
});
}).catch(console.error);
}
// Set the component to be rendered before we render the new route
function beforeEnter(el: HTMLElement) {
enteringEl = el;
}
// Enter the new route
function enter(_el: HTMLElement, done: TransitionDone) {
done();
}
// Vue transition stub functions
function afterEnter(_el: HTMLElement) { /* */ }
function afterLeave(_el: HTMLElement) { /* */ }
function beforeLeave(_el: HTMLElement) { /* */ }
function enterCancelled(_el: HTMLElement) { /* */ }
function leaveCancelled(_el: HTMLElement) { /* */ }

View File

@ -1,134 +0,0 @@
<template>
<ion-router-outlet
ref="ionRouterOutlet"
@click="catchIonicGoBack">
<router-view
v-if="customTransition"
:name="name"/>
<transition
v-else
:css="bindCSS"
mode="in-out"
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="enterCancelled"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="leaveCancelled"
>
<router-view :name="name"/>
</transition>
</ion-router-outlet>
</template>
<script lang="ts">
import { Prop } from 'vue-property-decorator';
import Component, { mixins } from 'vue-class-component';
import CatchIonicGoBack from '../mixins/catch-ionic-go-back';
import { IonRouterOutlet } from '../interfaces';
@Component
export default class IonVueRouter extends mixins(CatchIonicGoBack) {
@Prop({ default: 'default'}) name!: string;
@Prop({ default: false }) bindCSS!: boolean;
@Prop({ default: true }) animated!: boolean;
// Currently visible component
leavingEl: HTMLElement;
// Component to be rendered
enteringEl: HTMLElement;
// Flag to see if we're still in a transition
inTransition = false;
customTransition = false;
created() {
// Cancel navigation if there's a running transition
this.$router.beforeEach((to, _from, next) => {
this.customTransition = to.meta.customTransition || false
return this.$nextTick(() => {
return next(!this.inTransition as false);
})
})
}
transition(enteringEl: HTMLElement, leavingEl: HTMLElement) {
// Get the reference to the Ionic component handling the transitions
const ionRouterOutlet = this.$refs.ionRouterOutlet as IonRouterOutlet;
// The Ionic framework didn't load - skip animations
if (typeof ionRouterOutlet.componentOnReady === 'undefined') {
return;
}
// Skip animations if there's no component to navigate to
// or the current and the "to-be-rendered" components are the same
if (!enteringEl || enteringEl === leavingEl) {
return;
}
// Add the proper Ionic classes, important for smooth transitions
enteringEl.classList.add('ion-page', 'ion-page-invisible')
// Commit to the transition as soon as the Ionic Router Outlet is ready
return ionRouterOutlet.componentOnReady().then(el => {
return el.commit(enteringEl, leavingEl, {
deepWait: true,
duration: this.getDuration(),
direction: this.getDirection(),
showGoBack: this.$router.canGoBack(),
});
});
}
// Instant transition if we don't want to animate
getDuration() {
return !this.animated ? 0 : undefined;
}
// Get the navigation direction from the router
getDirection() {
return this.$router.direction === 1 ? 'forward' : 'back';
}
// Set the component to be rendered before we render the new route
beforeEnter(el: HTMLElement) {
this.enteringEl = el;
}
// Remember the current component before we leave the route
beforeLeave(el: HTMLElement) {
this.leavingEl = el;
}
// Transition when we leave the route
leave(el: HTMLElement, done: (opts?: boolean) => void): void {
const promise = this.transition(this.enteringEl, el);
this.inTransition = true;
// Skip any transition if we don't get back a Promise
if (!promise) {
this.inTransition = false;
return done();
}
// Perform navigation once the transition was finished
promise.then(() => {
this.inTransition = false;
return done(true);
});
}
// Enter the new route
enter(_el: HTMLElement, done: () => void): void {
done();
}
afterEnter(/* el */) {}
enterCancelled(/* el */) {}
afterLeave(/* el */) {}
leaveCancelled(/* el */) {}
}
</script>