's'
This commit is contained in:
95
app/components/NavigationLoadingProvider.tsx
Normal file
95
app/components/NavigationLoadingProvider.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, type ReactNode } from "react";
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import NavigationLoader from "./NavigationLoader";
|
||||
import {
|
||||
isInternalNavigationClick,
|
||||
startNavigation,
|
||||
stopNavigation,
|
||||
subscribeNavigationLoading,
|
||||
} from "@/app/lib/navigation-loading";
|
||||
|
||||
const LOADING_TIMEOUT_MS = 12000;
|
||||
|
||||
function shouldStartForHistoryUrl(nextUrl: string | URL | null | undefined) {
|
||||
if (!nextUrl) return false;
|
||||
try {
|
||||
const url = new URL(String(nextUrl), window.location.href);
|
||||
return (
|
||||
url.pathname !== window.location.pathname ||
|
||||
url.search !== window.location.search
|
||||
);
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default function NavigationLoadingProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
useEffect(() => subscribeNavigationLoading(setVisible), []);
|
||||
|
||||
useEffect(() => {
|
||||
stopNavigation();
|
||||
}, [pathname, searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) return;
|
||||
const timeout = window.setTimeout(() => stopNavigation(), LOADING_TIMEOUT_MS);
|
||||
return () => window.clearTimeout(timeout);
|
||||
}, [visible]);
|
||||
|
||||
useEffect(() => {
|
||||
const onDocumentClick = (event: MouseEvent) => {
|
||||
const target = event.target;
|
||||
if (!(target instanceof Element)) return;
|
||||
const anchor = target.closest("a");
|
||||
if (!(anchor instanceof HTMLAnchorElement)) return;
|
||||
if (isInternalNavigationClick(event, anchor)) {
|
||||
startNavigation();
|
||||
}
|
||||
};
|
||||
|
||||
const onPopState = () => startNavigation();
|
||||
|
||||
const originalPushState = history.pushState.bind(history);
|
||||
const originalReplaceState = history.replaceState.bind(history);
|
||||
|
||||
history.pushState = (...args) => {
|
||||
if (shouldStartForHistoryUrl(args[2])) {
|
||||
startNavigation();
|
||||
}
|
||||
return originalPushState(...args);
|
||||
};
|
||||
history.replaceState = (...args) => {
|
||||
if (shouldStartForHistoryUrl(args[2])) {
|
||||
startNavigation();
|
||||
}
|
||||
return originalReplaceState(...args);
|
||||
};
|
||||
|
||||
document.addEventListener("click", onDocumentClick, true);
|
||||
window.addEventListener("popstate", onPopState);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("click", onDocumentClick, true);
|
||||
window.removeEventListener("popstate", onPopState);
|
||||
history.pushState = originalPushState;
|
||||
history.replaceState = originalReplaceState;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
{visible ? <NavigationLoader /> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user