diff --git a/iso-router.tsx b/iso-router.tsx new file mode 100644 index 0000000..d8f9c45 --- /dev/null +++ b/iso-router.tsx @@ -0,0 +1,88 @@ +import * as Signal from "@preact/signals"; +import * as React from "react"; + +///////// Metas +type MetaFields = {title?:string, description?:string}; +type MetaRecord = MetaFields&{id:string} +const Stack = [] as MetaRecord[]; +const StackPush =(m:MetaRecord)=> {Stack.push(m); Update();} +const StackPop =(id:string)=> {Stack.splice(Stack.findIndex( item => item.id === id ), 1); Update();} +const Update =()=> document.title = Stack[Stack.length-1]?.title || "---"; +Update(); +export const useMeta=(fields:MetaFields)=> +{ + const id = React.useId(); + React.useEffect(()=>{ + StackPush({...fields, id}); + return ()=>StackPop(id); + }, []); +} +export const Meta =(props:MetaFields)=> { useMeta(props); return null; } + +//////// Router +//// Create Signals +export const pageURL = Signal.signal(new URL(globalThis.location.href || "")); +export const pagePath = Signal.signal([] as string[]); +Signal.effect(()=> pagePath.value = pageURL.value.pathname.split("/").filter(part=>part!="")); + +//// Add handlers +globalThis.addEventListener("click", e=> +{ + (e.composedPath() as HTMLAnchorElement[]).find((step)=>{ + if(step.href) + { + const url = new URL(step.href); + if(url.origin == document.location.origin) + { + e.preventDefault(); + history.pushState({}, "", url); + pageURL.value = url; + } + return true; + } + }) +}); +globalThis.addEventListener("popstate", _=> pageURL.value = new URL(globalThis.location.href) ); + +/// 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 +} +const context = React.createContext({nestedDepth:0, pathIndex:0, keys:{}} as RouteContextData); +export const useRoute =()=> React.useContext(context); +export const Route =(props:{path:string[], children:React.ReactNode|React.ReactNode[]}):React.JSX.Element|null=> +{ + // Match the current page url, with a route. + const {nestedDepth, pathIndex} = React.useContext(context); + const pagePathRange = pagePath.value.slice(pathIndex); + const emptyMatch = !props.path.length && !pagePathRange.length; + const comparisonSize = Math.min(props.path.length, pagePath.value.length); + + if(comparisonSize == 0 && emptyMatch === false) // one of the arrays is empty and one is not + { + return null; + } + + const keys = {} as Record; // not doing anything with this currently + for(let i=0; i{props.children} +} \ No newline at end of file