Files
gitlab-instance-0a899031_cn…/app/components/NavigationLoadingProvider.tsx
2026-06-08 06:49:21 -05:00

96 lines
2.5 KiB
TypeScript

"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}
</>
);
}