Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
# vue-router Navigation API Router

And finally, the back and forward browser buttons detected.

This branch using this [feat: add navigation-api router](https://github.com/vuejs/router/pull/2551) PR.

Now the `vue-router` supports the [Navigation API](https://wicg.github.io/navigation-api/) and you can use the browser back and forward buttons.

Now we can use native view or vue transitions, to switch between the transition modes, just add or remove the `viewTransition: true,` line in the `createClientRouter` call, refresh the page and navigate with the header links or back and forward browser buttons.

You can also switch between Navigation API and Web History API router by adding or removing the `navigationApi` option or use Firefox or Safari that don't support the Navigation API.

Unexpected result, using the [dom-navigation](https://github.com/virtualstate/navigation?tab=readme-ov-file#polyfill) polyfill, we are able to detect the back and forward buttons in Firefox (Safari not yet tested), but the navigation not being fired, so the view is not changing.

If you want to enable or disable the `dom-navigation` polyfill, just add or remove the script module in the index.html page.
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
<!--
<script type="module">import 'https://cdn.skypack.dev/@virtualstate/navigation/polyfill'</script>
-->
</head>
<body>
<div id="app"></div>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
},
"dependencies": {
"vue": "^3.5.21",
"vue-router": "^4.5.1"
"vue-router": "https://pkg.pr.new/vue-router@11632e0"
},
"devDependencies": {
"@antfu/eslint-config": "^5.2.2",
Expand Down
197 changes: 103 additions & 94 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

125 changes: 119 additions & 6 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,31 +1,144 @@
<script setup lang="ts">
import { watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'

const router = useRouter()
const route = useRoute()
watch(route, (to) => {
console.log('Route changed to:', to.fullPath)
})
router.beforeEach((to, from, next, info) => {
console.log('beforeEach: updating route to:', to.fullPath, 'from:', from?.fullPath, info)
next()
})
</script>

<template>
<div>
<header>
<h1>Navigation API test</h1>
<nav>
<RouterLink to="/">
Go Home
</RouterLink>
<RouterLink to="/a">
Go to A
</RouterLink>
<RouterLink to="/b">
Go to B
</RouterLink>
<RouterLink to="/scroll-restoration">
Go to Scrol restoration
</RouterLink>
</nav>
</header>
<main>
<RouterView />
<RouterView v-slot="{ Component, transitionMode }">
<div class="transition-mode-display">
Active Mode: <span>{{ transitionMode }}</span>
</div>

<transition
v-if="transitionMode === 'auto'"
name="slide-fade"
mode="out-in"
>
<component :is="Component" :key="route.path" />
</transition>

<component :is="Component" v-else :key="route.path" />
</RouterView>
</main>
</div>
</template>

<style>
/* --- 1. Animaciones para las View Transitions NATIVAS --- */

@keyframes slide-to-left {
from {
transform: translateX(0);
}
to {
transform: translateX(-30px);
opacity: 0;
}
}

@keyframes slide-from-right {
from {
transform: translateX(30px);
opacity: 0;
}
to {
transform: translateX(0);
}
}

/* `::view-transition-old(root)` es la captura de la página antigua.
La animamos para que se deslice hacia la izquierda y desaparezca.
*/
::view-transition-old(root) {
animation: 0.3s ease-out forwards slide-to-left;
}

/* `::view-transition-new(root)` es la nueva página.
La animamos para que entre deslizándose desde la derecha.
*/
::view-transition-new(root) {
animation: 0.3s ease-in forwards slide-from-right;
}
</style>

<style scoped>
header {
display: grid;
gap: 1rem;
grid-template-columns: auto;
padding: 1rem 2rem;
border-bottom: 1px solid #333;
margin-bottom: 2rem;
}
h1 {
margin: 0 0 1rem;
}
nav {
display: grid;
display: flex;
gap: 1rem;
grid-template-columns: auto;
}
nav a {
font-weight: bold;
color: #2c3e50;
text-decoration: none;
}
.router-link-active {
color: #42b983;
}
main {
padding: 0 2rem;
}

.transition-mode-display {
background-color: #f0f0f0;
border-left: 4px solid #42b983;
padding: 8px 12px;
margin-bottom: 2rem;
font-family: monospace;
font-size: 0.9rem;
}

.transition-mode-display span {
font-weight: bold;
color: #2c3e50;
}

/* --- 2. Animaciones para el <transition> de VUE (el fallback) --- */

.slide-fade-enter-active,
.slide-fade-leave-active {
transition: all 0.3s ease-out;
}

.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateY(20px);
opacity: 0;
}
</style>
62 changes: 47 additions & 15 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,61 @@
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createModernRouter, createRouter, createWebHistory } from 'vue-router'
import { routes } from 'vue-router/auto-routes'
import App from './App.vue'
import { createNavigationApiHistory } from './navigationApiHistory.ts'

// eslint-disable-next-line no-console
console.log(window.navigation)

const history = window.navigation
? createNavigationApiHistory()
: createWebHistory()

const router = createRouter({
history,
routes,
const router = createModernRouter({
legacy: {
factory(transitionMode) {
return createRouter({
enableScrollManagement: true,
focusManagement: true,
history: createWebHistory(),
routes,
}, transitionMode)
},
},
navigationApi: {
options: {
location: '',
routes,
},
},
viewTransition: true,
})

history.listen((to, from, info) => {
// eslint-disable-next-line no-console
console.log(`History: ${from} => ${to} => ${info.type}`)
router.enableViewTransition({
onStart(transition) {
// eslint-disable-next-line no-console
console.log('View transition started', transition)
},
onFinished(transition) {
// eslint-disable-next-line no-console
console.log('View transition finished', transition)
},
onAborted(transition) {
// eslint-disable-next-line no-console
console.log('View transition aborted', transition)
},
})

const app = createApp(App)
app.use(router)

router.afterEach((to, from) => {
// eslint-disable-next-line no-console
console.log(`Navigation to ${to.meta} from ${from?.fullPath}`)
// eslint-disable-next-line no-console
console.log(`AfterEach to ${to.fullPath} from ${from?.fullPath}`)
})

createApp(App).use(router).mount('#app')
app.mount('#app')

/* router.beforeEach((to, from, _) => {
// eslint-disable-next-line no-console
console.log(`BeforeEach to ${to.fullPath} from ${from?.fullPath}`)
}) */
router.isReady().then(() => {
// eslint-disable-next-line no-console
console.log('router is ready')
})
32 changes: 32 additions & 0 deletions src/pages/b.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
<script setup lang="ts">
import { shallowRef } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'

const step = shallowRef<'a' | 'b'>('a')

onBeforeRouteLeave((to, _from, next, info) => {
console.log('Leaving Page B to:', to.fullPath, info)
if (step.value === 'b' && info?.isBackBrowserButton) {
step.value = 'a'
// stay on the same page
next(false)
return
}
next()
})
</script>

<template>
<div>
<h2>Page B</h2>
<div v-if="step === 'a'">
<h3>Step A</h3>
<button @click="step = 'b'">
Go Step B
</button>
</div>
<div v-else>
<h3>Step B</h3>
<button @click="step = 'a'">
Go Step A
</button>

<p>Press back button, should go to Step A instead previous page</p>
</div>
</div>
</template>
Loading