@@ -69,6 +69,11 @@ import { addDevtools } from './devtools'
6969import { _LiteralUnion } from './types/utils'
7070import { RouteLocationAsRelativeTyped } from './typed-routes/route-location'
7171import { RouteMap } from './typed-routes/route-map'
72+ import {
73+ RouterViewTransition ,
74+ TransitionMode ,
75+ transitionModeKey ,
76+ } from './transition'
7277
7378/**
7479 * Internal type to define an ErrorHandler
@@ -205,6 +210,15 @@ export interface Router {
205210 */
206211 listening : boolean
207212
213+ /**
214+ * Enable native view transition.
215+ *
216+ * NOTE: will be a no-op if the browser does not support it.
217+ *
218+ * @param options The options to use.
219+ */
220+ enableViewTransition ( options : RouterViewTransition )
221+
208222 /**
209223 * Add a new {@link RouteRecordRaw | route record} as the child of an existing route.
210224 *
@@ -216,6 +230,7 @@ export interface Router {
216230 parentName : NonNullable < RouteRecordNameGeneric > ,
217231 route : RouteRecordRaw
218232 ) : ( ) => void
233+
219234 /**
220235 * Add a new {@link RouteRecordRaw | route record} to the router.
221236 *
@@ -382,7 +397,10 @@ export interface Router {
382397 *
383398 * @param options - {@link RouterOptions}
384399 */
385- export function createRouter ( options : RouterOptions ) : Router {
400+ export function createRouter (
401+ options : RouterOptions ,
402+ transitionMode : TransitionMode = 'auto'
403+ ) : Router {
386404 const matcher = createRouterMatcher ( options . routes , options )
387405 const parseQuery = options . parseQuery || originalParseQuery
388406 const stringifyQuery = options . stringifyQuery || originalStringifyQuery
@@ -1230,6 +1248,11 @@ export function createRouter(options: RouterOptions): Router {
12301248 let started : boolean | undefined
12311249 const installedApps = new Set < App > ( )
12321250
1251+ let beforeResolveTransitionGuard : ( ( ) => void ) | undefined
1252+ let afterEachTransitionGuard : ( ( ) => void ) | undefined
1253+ let onErrorTransitionGuard : ( ( ) => void ) | undefined
1254+ let popStateListener : ( ( ) => void ) | undefined
1255+
12331256 const router : Router = {
12341257 currentRoute,
12351258 listening : true ,
@@ -1255,6 +1278,26 @@ export function createRouter(options: RouterOptions): Router {
12551278 onError : errorListeners . add ,
12561279 isReady,
12571280
1281+ enableViewTransition ( options ) {
1282+ beforeResolveTransitionGuard ?.( )
1283+ afterEachTransitionGuard ?.( )
1284+ onErrorTransitionGuard ?.( )
1285+ if ( popStateListener ) {
1286+ window . removeEventListener ( 'popstate' , popStateListener )
1287+ }
1288+
1289+ if ( typeof document === 'undefined' || ! document . startViewTransition ) {
1290+ return
1291+ }
1292+
1293+ ; [
1294+ beforeResolveTransitionGuard ,
1295+ afterEachTransitionGuard ,
1296+ onErrorTransitionGuard ,
1297+ popStateListener ,
1298+ ] = enableViewTransition ( this , options )
1299+ } ,
1300+
12581301 install ( app : App ) {
12591302 app . component ( 'RouterLink' , RouterLink )
12601303 app . component ( 'RouterView' , RouterView )
@@ -1293,6 +1336,7 @@ export function createRouter(options: RouterOptions): Router {
12931336 app . provide ( routerKey , router )
12941337 app . provide ( routeLocationKey , shallowReactive ( reactiveRoute ) )
12951338 app . provide ( routerViewLocationKey , currentRoute )
1339+ app . provide ( transitionModeKey , transitionMode )
12961340
12971341 const unmountApp = app . unmount
12981342 installedApps . add ( app )
@@ -1356,3 +1400,94 @@ function extractChangingRecords(
13561400
13571401 return [ leavingRecords , updatingRecords , enteringRecords ]
13581402}
1403+
1404+ function isChangingPage (
1405+ to : RouteLocationNormalized ,
1406+ from : RouteLocationNormalized
1407+ ) {
1408+ if ( to === from || from === START_LOCATION ) {
1409+ return false
1410+ }
1411+
1412+ // If route keys are different then it will result in a rerender
1413+ if ( generateRouteKey ( to ) !== generateRouteKey ( from ) ) {
1414+ return true
1415+ }
1416+
1417+ const areComponentsSame = to . matched . every (
1418+ ( comp , index ) =>
1419+ comp . components &&
1420+ comp . components . default === from . matched [ index ] ?. components ?. default
1421+ )
1422+ return ! areComponentsSame
1423+ }
1424+
1425+ function enableViewTransition ( router : Router , options : RouterViewTransition ) {
1426+ let transition : undefined | ViewTransition
1427+ let hasUAVisualTransition = false
1428+ let finishTransition : ( ( ) => void ) | undefined
1429+ let abortTransition : ( ( ) => void ) | undefined
1430+
1431+ const defaultTransitionSetting =
1432+ options . transition ?. defaultViewTransition ?? true
1433+
1434+ const resetTransitionState = ( ) => {
1435+ transition = undefined
1436+ hasUAVisualTransition = false
1437+ abortTransition = undefined
1438+ finishTransition = undefined
1439+ }
1440+
1441+ function popStateListener ( event : PopStateEvent ) {
1442+ hasUAVisualTransition = event . hasUAVisualTransition
1443+ if ( hasUAVisualTransition ) {
1444+ transition ?. skipTransition ( )
1445+ }
1446+ }
1447+
1448+ window . addEventListener ( 'popstate' , popStateListener )
1449+
1450+ const beforeResolveTransitionGuard = router . beforeResolve ( ( to , from ) => {
1451+ const transitionMode = to . meta . viewTransition ?? defaultTransitionSetting
1452+ if (
1453+ hasUAVisualTransition ||
1454+ transitionMode === false ||
1455+ ( transitionMode !== 'always' &&
1456+ window . matchMedia ( '(prefers-reduced-motion: reduce)' ) . matches ) ||
1457+ ! isChangingPage ( to , from )
1458+ ) {
1459+ return
1460+ }
1461+
1462+ const promise = new Promise < void > ( ( resolve , reject ) => {
1463+ finishTransition = resolve
1464+ abortTransition = reject
1465+ } )
1466+
1467+ const transition = document . startViewTransition ( ( ) => promise )
1468+
1469+ options . onStart ?.( transition )
1470+ transition . finished
1471+ . then ( ( ) => options . onFinished ?.( transition ) )
1472+ . catch ( ( ) => options . onAborted ?.( transition ) )
1473+ . finally ( resetTransitionState )
1474+
1475+ return promise
1476+ } )
1477+
1478+ const afterEachTransitionGuard = router . afterEach ( ( ) => {
1479+ finishTransition ?.( )
1480+ } )
1481+
1482+ const onErrorTransitionGuard = router . onError ( ( ) => {
1483+ abortTransition ?.( )
1484+ resetTransitionState ( )
1485+ } )
1486+
1487+ return [
1488+ beforeResolveTransitionGuard ,
1489+ afterEachTransitionGuard ,
1490+ onErrorTransitionGuard ,
1491+ popStateListener ,
1492+ ]
1493+ }
0 commit comments