import { Children, Fragment, isValidElement, type ComponentProps, type ReactNode, } from 'react'; import type { Destination } from './State.js'; import type { PartialWithUndefined, URLProps } from './types.js'; export const nullOrigin = new URL('https://0'); export const popStateEventName = 'popstate'; export function withWindow(a: (window: Window) => A): A | undefined; export function withWindow(a: (window: Window) => A, b: B): A | B; export function withWindow( a: (window: Window) => A, b?: B, ): A | B | undefined { return typeof window !== 'undefined' ? a(window) : b; } export function withNavigation( fn: (navigation: Navigation) => A, ): A | undefined; export function withNavigation( fn: (navigation: Navigation) => A, b: B, ): A | B; export function withNavigation( fn: (navigation: Navigation) => A, b?: B, ): A | B | undefined { return withWindow((w) => (w.navigation ? fn(w.navigation) : b), b); } export function urlRhs(url: URL): string { return decodeURI(url.toString().slice(url.origin.length)); } export function urlObjectAssign( url: URL, props: PartialWithUndefined, ): URL { const { origin, searchParams, ...rest } = props; const newUrl = origin ? new URL(urlRhs(url), origin) : new URL(url); Object.entries(rest).forEach(([k, v]) => { newUrl[k as keyof typeof rest] = v || ''; }); if (searchParams) { newUrl.search = ''; searchParams.forEach((v, k) => newUrl.searchParams.append(k, v)); } return newUrl; } export function flattenFragments(children: React.ReactNode): ReactNode[] { return Children.toArray(children).flatMap((child): ReactNode[] => { if ( isValidElement>(child) && child.type === Fragment ) { return flattenFragments(child.props.children); } return [child]; }, []); } export function calculateUrl(href: Destination, currentUrl: URL) { if (href instanceof URL) { return href; } if (typeof href === 'string') { return new URL(href, currentUrl); } return urlObjectAssign(new URL(currentUrl), href); }