[READ-ONLY] a fast, modern browser for the npm registry
at main 91 lines 2.5 kB view raw
1/** 2 * Scoped View Transitions plugin. 3 * 4 * Only triggers the View Transition API when navigating between `/` and `/search` 5 * (the search-box morph animation). All other navigations are left untouched so 6 * they feel instant. 7 */ 8export default defineNuxtPlugin(nuxtApp => { 9 if (!document.startViewTransition) return 10 11 let transition: ViewTransition | undefined 12 let finishTransition: (() => void) | undefined 13 let hasUAVisualTransition = false 14 15 const resetTransitionState = () => { 16 transition = undefined 17 finishTransition = undefined 18 hasUAVisualTransition = false 19 } 20 21 // Respect browser-initiated visual transitions (e.g. swipe-back) 22 window.addEventListener('popstate', event => { 23 hasUAVisualTransition = 24 (event as PopStateEvent & { hasUAVisualTransition?: boolean }).hasUAVisualTransition ?? false 25 if (hasUAVisualTransition) { 26 transition?.skipTransition() 27 } 28 }) 29 30 const router = useRouter() 31 32 router.beforeResolve(async (to, from) => { 33 if (to.matched.length === 0) return 34 35 const toPath = to.path 36 const fromPath = from.path 37 38 // Only transition between / and /search 39 if (!isSearchTransition(toPath, fromPath)) return 40 41 // Respect prefers-reduced-motion 42 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return 43 44 // Skip if browser already handled the visual transition 45 if (hasUAVisualTransition) return 46 47 const promise = new Promise<void>(resolve => { 48 finishTransition = resolve 49 }) 50 51 let changeRoute: () => void 52 const ready = new Promise<void>(resolve => (changeRoute = resolve)) 53 54 transition = document.startViewTransition(() => { 55 changeRoute!() 56 return promise 57 }) 58 59 transition.finished.then(resetTransitionState) 60 61 await nuxtApp.callHook('page:view-transition:start', transition) 62 63 return ready 64 }) 65 66 // Abort on errors 67 router.onError(() => { 68 finishTransition?.() 69 resetTransitionState() 70 }) 71 nuxtApp.hook('app:error', () => { 72 finishTransition?.() 73 resetTransitionState() 74 }) 75 nuxtApp.hook('vue:error', () => { 76 finishTransition?.() 77 resetTransitionState() 78 }) 79 80 // Finish when page render completes 81 nuxtApp.hook('page:finish', () => { 82 finishTransition?.() 83 resetTransitionState() 84 }) 85}) 86 87/** Return true when navigating between `/` and `/search` (either direction). */ 88function isSearchTransition(toPath: string, fromPath: string): boolean { 89 const paths = new Set([toPath, fromPath]) 90 return paths.has('/') && paths.has('/search') 91}