|
"use client"; |
|
|
|
// Next.js version of React ViewTransition fixture |
|
// ref: https://github.com/facebook/react/blob/main/fixtures/view-transition/src/components/App.js |
|
// |
|
// Currently, it has a infinite rendering bug in React when you navigate for go or back |
|
// wait for merging following PRs |
|
// PR 1: https://github.com/facebook/react/pull/32821 |
|
// PR 2: https://github.com/vercel/next.js/pull/77856 |
|
// |
|
// You can use this like: |
|
// |
|
// export default function RootLayout({ |
|
// children, |
|
// }: Readonly<{ |
|
// children: React.ReactNode; |
|
// }>) { |
|
// return ( |
|
// <html> |
|
// <body> |
|
// <NavigationIntercepter>{children}</NavigationIntercepter> |
|
// </body> |
|
// </html> |
|
// ); |
|
// } |
|
// |
|
// example: |
|
// <a |
|
// onClick={(event) => { |
|
// event.preventDefault(); |
|
// window.navigation.navigate("/b"); |
|
// }} |
|
// href="/b" |
|
// > |
|
// Link |
|
// </a> |
|
|
|
import { useRouter } from "next/navigation"; |
|
import { |
|
startTransition, |
|
useEffect, |
|
useInsertionEffect, |
|
useState, |
|
unstable_ViewTransition as ViewTransition, |
|
unstable_addTransitionType as addTransitionType, |
|
} from "react"; |
|
|
|
export function NavigationIntercepter({ |
|
children, |
|
}: { |
|
children: React.ReactNode; |
|
}) { |
|
const [resolveNavigation, setWorkInProgressNavigation] = useState< |
|
null | (() => void) |
|
>(null); |
|
const router = useRouter(); |
|
|
|
useEffect(() => { |
|
function handleNavigate(event: NavigateEvent) { |
|
if (!event.canIntercept) { |
|
return; |
|
} |
|
const navigationType = event.navigationType; |
|
if (!event.userInitiated && navigationType !== "traverse") { |
|
return; |
|
} |
|
const previousIndex = |
|
window.navigation.currentEntry == null |
|
? 0 |
|
: window.navigation.currentEntry.index; |
|
const navigationDirection = |
|
navigationType === "traverse" |
|
? event.destination.index > previousIndex |
|
? "forward" |
|
: "back" |
|
: null; |
|
const newUrl = new URL(event.destination.url); |
|
const nextUrl = newUrl.pathname + newUrl.search; |
|
event.intercept({ |
|
handler() { |
|
const { promise, resolve } = Promise.withResolvers<void>(); |
|
startTransition(() => { |
|
addTransitionType("navigation-" + navigationType); |
|
if (navigationDirection != null) { |
|
addTransitionType("navigation-" + navigationDirection); |
|
} |
|
switch (navigationType) { |
|
case "push": |
|
router.push(nextUrl); |
|
break; |
|
case "replace": |
|
router.replace(nextUrl); |
|
break; |
|
case "reload": |
|
router.refresh(); |
|
break; |
|
case "traverse": |
|
router.replace(nextUrl); |
|
break; |
|
} |
|
setWorkInProgressNavigation(resolve); |
|
}); |
|
return promise; |
|
}, |
|
}); |
|
} |
|
window.navigation.addEventListener("navigate", handleNavigate); |
|
return () => { |
|
window.navigation.removeEventListener("navigate", handleNavigate); |
|
}; |
|
}, [router]); |
|
|
|
useInsertionEffect(() => { |
|
if (!resolveNavigation) { |
|
return; |
|
} |
|
resolveNavigation(); |
|
}, [resolveNavigation]); |
|
|
|
return <ViewTransition>{children}</ViewTransition>; |
|
} |