UNPKG

@virtualstate/navigation

Version:

Native JavaScript [navigation](https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API) implementation

148 lines (127 loc) 4.24 kB
import {Route, RouteType, URLPatternResult} from "./types"; import {isPromise, like} from "../is"; import {Event} from "../event-target"; import {NavigationDestination} from "../spec/navigation"; import {getRouterRoutes, isRouter, Router} from "./router"; import {exec} from "./url-pattern"; export async function transitionEvent<E extends Event, R>(router: Router<E, R>, event: E): Promise<void> { const promises: Promise<unknown>[] = []; const { signal, } = event; const url: URL = getURL(event); const { pathname } = url; transitionPart( "route", (route, match) => route.fn(event, match), handleResolve, handleReject ); if (promises.length) { await Promise.all(promises); } function transitionPart( type: RouteType, fn: (route: Route<E, R>, match?: URLPatternResult) => unknown, resolve = handleResolve, reject = handleReject ) { let isRoute = false; resolveRouter(router); return isRoute; function matchRoute(route: Route<E, R>, parentMatch?: URLPatternResult) { const { router, pattern, string } = route; let match = parentMatch; if (string) { if (string !== pathname) { return; } } else if (pattern) { match = exec(pattern, url); if (!match) return; } if (isRouter<E, R>(router)) { return resolveRouter(router, match); } isRoute = true; try { const maybe = fn(route, match); if (isPromise(maybe)) { promises.push( maybe .then(resolve) .catch(reject) ); } else { resolve(maybe); } } catch (error) { reject(error); } } function resolveRouter(router: Router<E, R>, match?: URLPatternResult) { const routes = getRouterRoutes(router); resolveRoutes(routes[type]); resolveRoutes(routes.router); function resolveRoutes(routes: Route<E, R>[]) { for (const route of routes) { if (signal?.aborted) break; matchRoute(route, match); } } } } function noop() {} function handleResolve(value: unknown) { transitionPart( "resolve", (route, match) => route.fn(value, event, match), noop, handleReject ); } function handleReject(error: unknown) { const isRoute = transitionPart( "reject", (route, match) => route.fn(error, event, match), noop, (error) => Promise.reject(error) ); if (!isRoute) { throw error; } } function getURL<E extends Event>(event: E) { if (isDestination(event)) { return new URL(event.destination.url); } else if (isRequest(event)) { return new URL(event.request.url); } else if (isURL(event)) { return new URL(event.url); } throw new Error("Could not get url from event"); function isDestination(event: E): event is E & { destination: NavigationDestination } { return ( like<{ destination: unknown }>(event) && !!event.destination ) } function isRequest(event: E): event is E & { request: Request } { return ( like<{ request: unknown }>(event) && !!event.request ) } function isURL(event: E): event is E & { url: string | URL } { return ( like<{ url: unknown }>(event) && !!( event.url && ( typeof event.url === "string" || event.url instanceof URL ) ) ) } } }