From e357edf92ba7dc79ef808e4cbac28811db2eb2af Mon Sep 17 00:00:00 2001 From: gwendall Date: Fri, 17 Mar 2023 05:29:52 +0100 Subject: [PATCH] Enable client-side nav detection --- src/index.tsx | 189 ++++++++++++++++++++++++++++---------------------- 1 file changed, 106 insertions(+), 83 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 19d0be5..476c8b8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,3 +1,5 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-explicit-any */ /** * * NextTopLoader @@ -10,7 +12,7 @@ import * as PropTypes from 'prop-types'; import * as React from 'react'; import * as NProgress from 'nprogress'; -export interface NextTopLoaderProps { +export type NextTopLoaderProps = { /** * Color for the TopLoader. * @default "#29d" @@ -27,7 +29,7 @@ export interface NextTopLoaderProps { */ crawlSpeed?: number; /** - * The height for the TopLoader. + * The height for the TopLoader in pixels (px). * @default 3 */ height?: number; @@ -53,99 +55,120 @@ export interface NextTopLoaderProps { speed?: number; } -const NextTopLoader = (props: NextTopLoaderProps) => { - const color = '#29d'; - const height = 3; +const NextTopLoader = ({ + color, + height, + showSpinner, + crawl, + crawlSpeed, + initialPosition, + easing, + speed, +}: NextTopLoaderProps) => { + const defaultColor = '#29d'; + const defaultHeight = 3; const styles = ( ); React.useEffect(() => { - if (props.showSpinner !== undefined) { - NProgress.configure({ showSpinner: props.showSpinner }); - } - if (props.crawl !== undefined) { - NProgress.configure({ trickle: props.crawl }); - } - if (props.crawlSpeed !== undefined) { - NProgress.configure({ trickleSpeed: props.crawlSpeed }); - } - if (props.initialPosition !== undefined) { - NProgress.configure({ minimum: props.initialPosition }); - } - if (props.easing !== undefined) { - NProgress.configure({ easing: props.easing }); - } - if (props.speed !== undefined) { - NProgress.configure({ speed: props.speed }); + NProgress.configure({ + showSpinner: showSpinner ?? true, + trickle: crawl ?? true, + trickleSpeed: crawlSpeed ?? 200, + minimum: initialPosition ?? 0.08, + easing: easing ?? 'ease', + speed: speed ?? 200, + }); + + function isAnchorOfCurrentUrl(currentUrl: string, newUrl: string) { + const currentUrlObj = new URL(currentUrl); + const newUrlObj = new URL(newUrl); + // Compare hostname, pathname, and search parameters + if ( + currentUrlObj.hostname === newUrlObj.hostname && + currentUrlObj.pathname === newUrlObj.pathname && + currentUrlObj.search === newUrlObj.search + ) { + // Check if the new URL is just an anchor of the current URL page + const currentHash = currentUrlObj.hash; + const newHash = newUrlObj.hash; + return ( + currentHash !== newHash && currentUrlObj.href.replace(currentHash, '') === newUrlObj.href.replace(newHash, '') + ); + } + return false; } + // eslint-disable-next-line no-var var npgclass = document.querySelectorAll('html'); - let navLinks = document.querySelectorAll('a'); - navLinks.forEach((navLink) => { - navLink.addEventListener('click', (event: MouseEvent) => { - let currentUrl = window.location.href; - let newUrl = (event.currentTarget as HTMLAnchorElement).href; - let isExternalLink = (event.currentTarget as HTMLAnchorElement).target === "_blank"; - function isAnchorOfCurrentUrl(currentUrl: string, newUrl: string) { - const currentUrlObj = new URL(currentUrl); - const newUrlObj = new URL(newUrl); - // Compare hostname, pathname, and search parameters - if ( - currentUrlObj.hostname === newUrlObj.hostname && - currentUrlObj.pathname === newUrlObj.pathname && - currentUrlObj.search === newUrlObj.search - ) { - // Check if the new URL is just an anchor of the current URL page - const currentHash = currentUrlObj.hash; - const newHash = newUrlObj.hash; - return ( - currentHash !== newHash && - currentUrlObj.href.replace(currentHash, '') === newUrlObj.href.replace(newHash, '') - ); + function findClosestAnchor(element: HTMLElement | null): HTMLAnchorElement | null { + while (element && element.tagName.toLowerCase() !== 'a') { + element = element.parentElement; + } + return element as HTMLAnchorElement; + } + function handleClick(event: MouseEvent) { + try { + const target = event.target as HTMLElement; + const anchor = findClosestAnchor(target); + if (anchor) { + const currentUrl = window.location.href; + const newUrl = (anchor as HTMLAnchorElement).href; + const isExternalLink = (anchor as HTMLAnchorElement).target === "_blank"; + const isAnchor = isAnchorOfCurrentUrl(currentUrl, newUrl); + if (newUrl === currentUrl || isAnchor || isExternalLink) { + NProgress.start(); + NProgress.done(); + [].forEach.call(npgclass, function (el: Element) { + el.classList.remove("nprogress-busy"); + }); + } else { + NProgress.start(); + (function (history) { + const pushState = history.pushState; + history.pushState = function () { + NProgress.done(); + [].forEach.call(npgclass, function (el: Element) { + el.classList.remove("nprogress-busy"); + }); + return pushState.apply(history, arguments as any); + }; + })(window.history); } - return false; - } - const isAnchor = isAnchorOfCurrentUrl(currentUrl, newUrl); - if (newUrl === currentUrl || isAnchor || isExternalLink) { - NProgress.start(); - NProgress.done(); - [].forEach.call(npgclass, function (el: Element) { - el.classList.remove('nprogress-busy'); - }); - } else { - NProgress.start(); - (function (history) { - // eslint-disable-next-line no-var - var pushState = history.pushState; - history.pushState = function () { - NProgress.done(); - [].forEach.call(npgclass, function (el: Element) { - el.classList.remove('nprogress-busy'); - }); - // eslint-disable-next-line prefer-rest-params - return pushState.apply(history, arguments); - }; - })(window.history); } - }); - }); - }); + } catch (err) { + console.log('NextTopLoader error: ', err); + } + } + + // Add the global click event listener + document.addEventListener("click", handleClick); + + // Clean up the global click event listener when the component is unmounted + return () => { + document.removeEventListener("click", handleClick); + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); return styles; };