Repository: dotnet/aspnetcore
File: src/Components/Web.JS/src/Services/NavigationUtils.ts (TypeScript)

Code before:
  if (anchorTarget && canProcessAnchor(anchorTarget)) {
    const anchorHref = anchorTarget.getAttribute('href')!;

    const absoluteHref = toAbsoluteUri(anchorHref);

    if (isWithinBaseUriSpace(absoluteHref)) {
      event.preventDefault();
      callbackIfIntercepted(absoluteHref);
    }
  }
}

export function isWithinBaseUriSpace(href: string) {
  const baseUriWithoutTrailingSlash = toBaseUriWithoutTrailingSlash(document.baseURI!);
  const nextChar = href.charAt(baseUriWithoutTrailingSlash.length);

  return href.startsWith(baseUriWithoutTrailingSlash)
  && (nextChar === '' || nextChar === '/' || nextChar === '?' || nextChar === '#');
}

export function isSamePageWithHash(absoluteHref: string): boolean {
  const url = new URL(absoluteHref);
  return url.hash !== '' && location.origin === url.origin && location.pathname === url.pathname && location.search === url.search;
}

export function isHashOnlyChange(oldUrl: string, newUrl: string): boolean {
  try {
    const a = new URL(oldUrl);
    const b = new URL(newUrl);
    return a.origin === b.origin && a.pathname === b.pathname
      && a.search === b.search && a.hash !== b.hash;
  } catch {
    return false;
  }
}

export function isForSamePath(url1: string, url2: string) {
  // We are going to use the scheme, host, port and path to determine if the two URLs are compatible.
  // We do not account for the query string as we want to allow for the query string to change.
  // (Blazor doesn't use the query string for routing purposes).
  const parsedUrl1 = new URL(url1);
  const parsedUrl2 = new URL(url2);
  return parsedUrl1.protocol === parsedUrl2.protocol
    && parsedUrl1.host === parsedUrl2.host
    && parsedUrl1.port === parsedUrl2.port
    && parsedUrl1.pathname === parsedUrl2.pathname;
}

export function performScrollToElementOnTheSamePage(absoluteHref : string): void {
  const hashIndex = absoluteHref.indexOf('#');
  if (hashIndex === absoluteHref.length - 1) {

Review comment (suggestion):
This is partially duplicating `isSamePageWithHash` in the meaning: I believe we could use your `isHashOnlyChange` in the place we're using `isSamePageWithHash` currently and remove `isSamePageWithHash`, to not to keep two very similar methods.
I would rather call the final method `isSameUrlWithDifferentHash` or `urlsDifferOnlyWithHash`. Using `change` in the name could imply that the method changes something.

Code after:
  if (anchorTarget && canProcessAnchor(anchorTarget)) {
    const anchorHref = anchorTarget.getAttribute('href')!;

    const absoluteHref = toAbsoluteUri(anchorHref);

    if (isWithinBaseUriSpace(absoluteHref)) {
      event.preventDefault();
      callbackIfIntercepted(absoluteHref);
    }
  }
}

export function isWithinBaseUriSpace(href: string) {
  const baseUriWithoutTrailingSlash = toBaseUriWithoutTrailingSlash(document.baseURI!);
  const nextChar = href.charAt(baseUriWithoutTrailingSlash.length);

  return href.startsWith(baseUriWithoutTrailingSlash)
  && (nextChar === '' || nextChar === '/' || nextChar === '?' || nextChar === '#');
}

export function isSamePageWithHash(oldUrl: string, newUrl: string): boolean {
  const a = new URL(oldUrl);
  const b = new URL(newUrl);
  return a.origin === b.origin && a.pathname === b.pathname
    && a.search === b.search && b.hash !== '';
}

export function isForSamePath(url1: string, url2: string) {
  // We are going to use the scheme, host, port and path to determine if the two URLs are compatible.
  // We do not account for the query string as we want to allow for the query string to change.
  // (Blazor doesn't use the query string for routing purposes).
  const parsedUrl1 = new URL(url1);
  const parsedUrl2 = new URL(url2);
  return parsedUrl1.protocol === parsedUrl2.protocol
    && parsedUrl1.host === parsedUrl2.host
    && parsedUrl1.port === parsedUrl2.port
    && parsedUrl1.pathname === parsedUrl2.pathname;
}

export function performScrollToElementOnTheSamePage(absoluteHref : string): void {
  const hashIndex = absoluteHref.indexOf('#');
  if (hashIndex === absoluteHref.length - 1) {
    return;
  }

  const identifier = absoluteHref.substring(hashIndex + 1);
  scrollToElement(identifier);
}

export function scrollToElement(identifier: string): void {
  document.getElementById(identifier)?.scrollIntoView();