diff --git a/vue/rollup.config.js b/vue/rollup.config.js
index 392cac5ff49..2aca83ffe50 100644
--- a/vue/rollup.config.js
+++ b/vue/rollup.config.js
@@ -41,7 +41,14 @@ function baseConfig() {
'@ionic/core/dist/ionic/svg',
'ionicons/dist/collection/icon/icon.css',
],
- plugins: [vue(), typescript({ useTsconfigDeclarationDir: true })],
+ plugins: [
+ vue(),
+ typescript({
+ useTsconfigDeclarationDir: true,
+ objectHashIgnoreUnknownHack: true,
+ clean: true
+ })
+ ],
}
}
diff --git a/vue/src/components/ion-vue-router-transitionless.vue b/vue/src/components/ion-vue-router-transitionless.vue
deleted file mode 100644
index 1695a63801a..00000000000
--- a/vue/src/components/ion-vue-router-transitionless.vue
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
diff --git a/vue/src/components/ion-vue-router.ts b/vue/src/components/ion-vue-router.ts
new file mode 100644
index 00000000000..41f4f6881ce
--- /dev/null
+++ b/vue/src/components/ion-vue-router.ts
@@ -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) { /* */ }
diff --git a/vue/src/components/ion-vue-router.vue b/vue/src/components/ion-vue-router.vue
deleted file mode 100644
index 97028607bef..00000000000
--- a/vue/src/components/ion-vue-router.vue
+++ /dev/null
@@ -1,134 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/vue/src/interfaces.ts b/vue/src/interfaces.ts
index 5c2b0e35619..35ea96b3971 100644
--- a/vue/src/interfaces.ts
+++ b/vue/src/interfaces.ts
@@ -6,6 +6,7 @@ declare module 'vue-router/types/router' {
interface VueRouter {
direction: number;
directionOverride: number | null;
+ transition: Promise;
canGoBack(): boolean;
}
}
@@ -60,8 +61,8 @@ export interface ApiCache {
}
export interface RouterArgs extends RouterOptions {
- direction: number;
- viewCount: number;
+ direction?: number;
+ viewCount?: number;
}
export interface ProxyControllerInterface {
diff --git a/vue/src/ionic.ts b/vue/src/ionic.ts
index 51ec3383b28..a5fa1d3e718 100644
--- a/vue/src/ionic.ts
+++ b/vue/src/ionic.ts
@@ -11,7 +11,7 @@ import {
import { IonicConfig } from '@ionic/core';
import { appInitialize } from './app-initialize';
import { VueDelegate } from './controllers/vue-delegate';
-import IonTabs from './components/navigation/IonTabs';
+import IonTabs from './components/navigation/ion-tabs';
export interface Controllers {
actionSheetController: ActionSheetController;
diff --git a/vue/src/mixins/catch-ionic-go-back.ts b/vue/src/mixins/catch-ionic-go-back.ts
deleted file mode 100644
index f7957a33861..00000000000
--- a/vue/src/mixins/catch-ionic-go-back.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import Vue from 'vue';
-import Router from '../router';
-import Component from 'vue-class-component';
-
-@Component
-export default class CatchIonicGoBack extends Vue {
- // Catch the bubbled-up event from the Ionic's back button
- catchIonicGoBack(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 = this.$router as 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);
- }
- }
-}
diff --git a/vue/src/router.ts b/vue/src/router.ts
index e6a7c3436f5..1f4ea038ffc 100644
--- a/vue/src/router.ts
+++ b/vue/src/router.ts
@@ -1,18 +1,11 @@
import VueRouter, { Route } from 'vue-router';
import { PluginFunction } from 'vue';
-import { RouterArgs, VueWindow } from './interfaces';
-import IonVueRouter from './components/ion-vue-router.vue';
-import IonVueRouterTransitionless from './components/ion-vue-router-transitionless.vue';
+import { RouterArgs } from './interfaces';
+import IonVueRouter from './components/ion-vue-router';
import { BackButtonEvent } from '@ionic/core';
-const vueWindow = window as VueWindow;
-const inBrowser: boolean = typeof window !== 'undefined';
-
-// Detect environment (browser, module, etc.)
-const _VueRouter: typeof VueRouter = inBrowser && vueWindow.VueRouter ? vueWindow.VueRouter : VueRouter;
-
// Extend the official VueRouter
-export default class Router extends _VueRouter {
+export default class Router extends VueRouter {
direction: number;
directionOverride: number | null;
viewCount: number;
@@ -39,15 +32,26 @@ export default class Router extends _VueRouter {
// Extend the existing history object
this.extendHistory();
+ // Wait for transition to finish before confirming navigation
+ this.extendTransitionConfirmation();
+
// Listen to Ionic's back button event
document.addEventListener('ionBackButton', (e: Event) => {
- (e as BackButtonEvent).detail.register(0, () => {
- this.back();
- });
+ (e as BackButtonEvent).detail.register(0, () => this.back());
});
}
- extendHistory(): void {
+ extendTransitionConfirmation() {
+ this.history._confirmTransition = this.history.confirmTransition;
+ this.history.confirmTransition = async (...opts: any) => {
+ if (undefined !== this.transition) {
+ await this.transition;
+ }
+ this.history._confirmTransition(...opts);
+ };
+ }
+
+ extendHistory() {
// Save a reference to the original method
this.history._updateRoute = this.history.updateRoute;
@@ -101,7 +105,7 @@ export default class Router extends _VueRouter {
}
}
-Router.install = (Vue, { disableIonicTransitions = false }: { disableIonicTransitions?: boolean } = {}): void => {
+Router.install = (Vue) => {
// If already installed - skip
if (Router.installed) {
return;
@@ -110,14 +114,8 @@ Router.install = (Vue, { disableIonicTransitions = false }: { disableIonicTransi
Router.installed = true;
// Install the official VueRouter
- _VueRouter.install(Vue);
+ VueRouter.install(Vue);
// Register the IonVueRouter component globally
- // either with default Ionic transitions turned on or off
- Vue.component('IonVueRouter', disableIonicTransitions ? IonVueRouterTransitionless : IonVueRouter);
+ Vue.component('IonVueRouter', IonVueRouter);
};
-
-// Auto-install when Vue is found (i.e. in browser via