404 fallback routes

This commit is contained in:
Seth Trowbridge 2024-05-16 14:30:04 -04:00
parent fadb797c55
commit 11359a906f
2 changed files with 36 additions and 38 deletions

24
app.tsx
View File

@ -2,8 +2,8 @@ import {Route, useRoute} from ">able/iso-router.tsx";
const Tracer =()=> const Tracer =()=>
{ {
const {nestedDepth, pathIndex} = useRoute(); const {nestedDepth, pathIndex, blocked} = useRoute();
return <div>Depth: {nestedDepth}, Index:{pathIndex}</div> return <div>Depth: {nestedDepth}, Index:{pathIndex}, Match:{blocked.value}</div>
} }
export default ()=><div> export default ()=><div>
@ -11,23 +11,17 @@ export default ()=><div>
<nav class="flex gap-10 p-6"> <nav class="flex gap-10 p-6">
<a href="/">Home</a> <a href="/">Home</a>
<a href="/about">About</a> <a href="/about">About</a>
<a href="/404">404</a>
</nav> </nav>
<Route path={[]}> <Route path={[]}><p>home page!</p></Route>
<p>home page! <Tracer/></p>
</Route>
<Route path={["about"]}> <Route path={["about"]}>
<nav class="flex gap-10 p-6"> <nav class="flex gap-10 p-6">
<a href="/about/more">more</a> <a href="/about/more">more</a>
<a href="/about/404">more 404</a>
</nav> </nav>
<Route path={[]}> <Route path={[]}><p>About page!</p></Route>
<Route path={["more"]}><p>more!</p></Route>
<p>About page! <Tracer/></p> <Route><span>couldnt find it</span></Route>
</Route>
<Route path={["more"]}>
<p>more!<Tracer/></p>
</Route>
</Route> </Route>
<Route><p>404 :(</p></Route>
</div>; </div>;

View File

@ -22,10 +22,21 @@ export const Meta =(props:MetaFields)=> { useMeta(props); return null; }
//////// Router //////// Router
type RouteContextData = {
/** Current nested depth of the router */ nestedDepth:number,
/** Index into the page path to start matching routes */ pathIndex:number,
/** Collection of keys from matched routes ("page/:key/") */ keys: Record<string, string>,
blocked: Signal.Signal<string | false>
}
const context = React.createContext({nestedDepth:0, pathIndex:0, keys:{}, blocked:Signal.signal(false as false|string)} as RouteContextData);
//// Create Signals //// Create Signals
export const pageURL = Signal.signal(new URL(globalThis?.location.href || "")); export const pageURL = Signal.signal(new URL(globalThis?.location.href || ""));
export const pagePath = Signal.signal([] as string[]); export const pagePath = Signal.signal([] as string[]);
Signal.effect(()=> pagePath.value = pageURL.value.pathname.split("/").filter(part=>part!="")); Signal.effect(()=>{
//@ts-ignore 1337 hax
if(context){context.__.blocked.value = false;}
pagePath.value = pageURL.value.pathname.split("/").filter(part=>part!="");
});
//// Add handlers //// Add handlers
globalThis.addEventListener("click", e=> globalThis.addEventListener("click", e=>
{ {
@ -44,14 +55,7 @@ globalThis.addEventListener("click", e=>
}) })
}); });
globalThis.addEventListener("popstate", _=> pageURL.value = new URL(globalThis.location.href) ); globalThis.addEventListener("popstate", _=> pageURL.value = new URL(globalThis.location.href) );
/// Rendering context //// Rendering context
type RouteContextData = {
/** Current nested depth of the router */ nestedDepth:number,
/** Index into the page path to start matching routes */ pathIndex:number,
/** Collection of keys from matched routes ("page/:key/") */ keys: Record<string, string>,
blocked: Signal.Signal<string | false>
}
const context = React.createContext({nestedDepth:0, pathIndex:0, keys:{}, blocked:Signal.signal(false as false|string)} as RouteContextData);
type PathFields = {pathAfter:string[], pathBefore:[], compare:typeof compare} type PathFields = {pathAfter:string[], pathBefore:[], compare:typeof compare}
const compare =(pathA:string[], pathB:string[])=> const compare =(pathA:string[], pathB:string[])=>
{ {
@ -88,21 +92,21 @@ export const useRoute =()=> {
const pathBefore = pathFull.slice(0, Math.max(0, routeContext.pathIndex-1)); const pathBefore = pathFull.slice(0, Math.max(0, routeContext.pathIndex-1));
return { ...routeContext, pathAfter, pathBefore, compare } as PathFields&RouteContextData; return { ...routeContext, pathAfter, pathBefore, compare } as PathFields&RouteContextData;
} }
export const Route =(props:{path:string[], children:React.ReactNode|React.ReactNode[]}):React.JSX.Element|null=> export const Route =(props:{path?:string[], children:React.ReactNode|React.ReactNode[]}):React.JSX.Element|null=>
{ {
const {nestedDepth, pathIndex, pathAfter, compare, keys} = useRoute(); const {nestedDepth, pathIndex, pathAfter, compare, keys, blocked} = useRoute();
const check = compare(props.path, pathAfter);
if (check) if(blocked.peek()){ return null; }
const passOn:RouteContextData = { nestedDepth:nestedDepth+1, pathIndex, keys, blocked:Signal.signal(false) }
if(props.path)
{ {
return <context.Provider value={{ const check = compare(props.path, pathAfter);
nestedDepth:nestedDepth+1, if (!check){ return null; }
pathIndex:pathIndex+check.comparisonSize,
keys: {...keys, ...check.keys}, blocked.value = "/"+props.path.join("/");
blocked:Signal.signal(false) passOn.pathIndex += check.comparisonSize;
}}>{props.children}</context.Provider>; passOn.keys = {...keys, ...check.keys};
}
else
{
return null;
} }
return <context.Provider value={passOn}>{props.children}</context.Provider>;
} }