import React, { useState, useEffect, useMemo } from "react"; import { useKeyboard } from "./hooks/use-keyboard.ts"; export type Route, K extends keyof T> = ( router: ToRoute, ) => React.FC; export function router>() { return { build: (_: K, route: Route) => { return route; }, withRoutes(..._: K[]) { type Filtered = { [K2 in K]: T[K2] }; return { build: (_: K, route: Route) => { return route; }, }; }, route: (componentBuilders: RouterComponents) => { return new RouteBuilder(componentBuilders); }, }; } type RouterComponents> = { [K in keyof T]: Route }; export function Back({ go, children }: { go: () => any; children: React.ReactNode }) { useKeyboard(event => { if (event.key === "Escape") go(); }); return <>{children}; } export class RouteBuilder> { Root: (initial: { route: Initial; props: T[Initial]; }) => React.ReactNode; constructor(componentBuilders: RouterComponents) { this.Root = (initial: { route: Initial; props: T[Initial] }) => { const router = useMemo(() => { return new Router(initial); }, []); const [current, setCurrent] = useState(router.current()); const Current = useMemo(() => { const minirouter: Partial> = {}; for (const key of Object.keys(componentBuilders)) { // @ts-ignore minirouter[key] = props => { router.route({ from: current.route, to: key, props, }); }; } const builder = componentBuilders[current.route]; return builder(minirouter as ToRoute); }, [current]); useEffect(() => { const listener = router.addRouteListener((route, props) => { setCurrent({ route, props, }); }); setCurrent(router.current()); return () => router.removeRouteListener(listener); }, [router]); return ; }; } } export type ToRoute> = { [K in keyof T]: (props: T[K]) => void }; class Router, Initial extends keyof T> { private _current: { route: keyof T; props: T[keyof T]; }; private _routeChangeCallbacks: Array<(route: K, data: T[K]) => any> = []; constructor(initial: { route: Initial; props: T[Initial] }) { this._current = initial; } current() { return { ...this._current, }; } route({ from, to, props }: { from: keyof T; to: K; props: T[K] }) { if (this._current.route === from) { this._current = { route: to, props, }; this.onRouteChange(to, props); } } addRouteListener(listener: (route: K, props: T[K]) => any) { this._routeChangeCallbacks.push(listener); return listener; } removeRouteListener(listener: (route: K, props: T[K]) => any) { const index = this._routeChangeCallbacks.indexOf(listener); if (index >= 0) this._routeChangeCallbacks.splice(index, 1); } private onRouteChange(route: K, props: T[K]) { for (const cb of this._routeChangeCallbacks) { cb(route, props); } } }