UNPKG

flexium

Version:

A lightweight, signals-based UI framework with cross-platform renderers

1 lines 17 kB
{"version":3,"sources":["../src/router/core.ts","../src/router/utils.ts","../src/router/context.ts","../src/router/components.ts"],"names":["createLocation","getLoc","parseQuery","loc","signal","navigate","path","search","params","query","value","key","matchPath","pathname","routePath","paramNames","regexPath","_","paramName","regex","match","index","createRoutesFromChildren","children","routes","childArray","child","component","nestedChildren","route","matchRoutes","location","result","matchRouteBranch","parentPath","fullPath","isLeaf","matcher","compilePath","matchedPath","paramValues","paramsObj","extractParams","currentMatch","childMatches","indexRoute","c","prefix","values","i","RouterCtx","createContext","RouteDepthCtx","useRouter","ctx","useContext","Router","props","matches","computed","m","routerContext","ms","rootMatch","RootComponent","h","Route","_props","Outlet","router","depth","Component","Link","handleClick"],"mappings":"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","file":"chunk-4H6G5CMD.mjs","sourcesContent":["import { signal, Signal } from '../core/signal';\nimport { Location } from './types';\n\nexport function createLocation(): [Signal<Location>, (path: string) => void] {\n const getLoc = (): Location => ({\n pathname: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n query: parseQuery(window.location.search)\n });\n\n const loc = signal(getLoc());\n\n const navigate = (path: string) => {\n window.history.pushState({}, '', path);\n loc.value = getLoc();\n };\n\n window.addEventListener('popstate', () => {\n loc.value = getLoc();\n });\n\n return [loc, navigate];\n}\n\nfunction parseQuery(search: string): Record<string, string> {\n const params = new URLSearchParams(search);\n const query: Record<string, string> = {};\n params.forEach((value, key) => {\n query[key] = value;\n });\n return query;\n}\n\nexport function matchPath(\n pathname: string,\n routePath: string\n): { matches: boolean; params: Record<string, string> } {\n const paramNames: string[] = [];\n const regexPath = routePath.replace(/:([^/]+)/g, (_, paramName) => {\n paramNames.push(paramName);\n return '([^/]+)';\n });\n\n const regex = new RegExp(`^${regexPath}$`);\n const match = pathname.match(regex);\n\n if (!match) {\n return { matches: false, params: {} };\n }\n\n const params: Record<string, string> = {};\n match.slice(1).forEach((value, index) => {\n params[paramNames[index]] = value;\n });\n\n return { matches: true, params };\n}\n","import { RouteDef, RouteMatch } from './types';\n\n/**\n * Flatten the children of <Router> or <Route> into a route configuration tree.\n * Note: This assumes `children` are VNodes representing <Route> components.\n */\nexport function createRoutesFromChildren(children: any): RouteDef[] {\n const routes: RouteDef[] = [];\n \n const childArray = Array.isArray(children) ? children : [children];\n // console.log('Parsing children:', childArray.length);\n \n for (const child of childArray) {\n if (!child || !child.props) {\n continue;\n }\n \n const { path, index, component } = child.props;\n const nestedChildren = child.children;\n \n const route: RouteDef = {\n path: path || '',\n index: !!index,\n component,\n children: nestedChildren ? createRoutesFromChildren(nestedChildren) : []\n };\n \n routes.push(route);\n }\n \n return routes;\n}\n\n/**\n * Match a URL against a route tree.\n * Returns an array of matches (from root to leaf).\n */\nexport function matchRoutes(routes: RouteDef[], location: string): RouteMatch[] | null {\n // console.log('Matching routes:', routes.length, 'against', location);\n for (const route of routes) {\n const result = matchRouteBranch(route, location, '');\n if (result) return result;\n }\n return null;\n}\n\nfunction matchRouteBranch(route: RouteDef, location: string, parentPath: string): RouteMatch[] | null {\n let fullPath = parentPath;\n if (route.path) {\n fullPath = parentPath.replace(/\\/$/, '') + '/' + route.path.replace(/^\\//, '');\n }\n \n const isLeaf = route.children.length === 0;\n const matcher = compilePath(fullPath, !isLeaf); \n const match = location.match(matcher);\n \n // console.log('Checking branch:', fullPath, 'leaf:', isLeaf, 'match:', !!match, 'regex:', matcher);\n \n if (match) {\n const [matchedPath, ...paramValues] = match;\n const paramsObj = extractParams(fullPath, paramValues);\n \n const currentMatch: RouteMatch = {\n route,\n params: paramsObj,\n pathname: matchedPath\n };\n \n if (isLeaf) {\n // Exact match required for leaf\n if (matchedPath === location) return [currentMatch];\n return null;\n }\n \n // Has children: try to match one of them\n // If no children match, and this route is an index route?\n // Or if this route matches partially, maybe an index child matches the rest?\n \n for (const child of route.children) {\n const childMatches = matchRouteBranch(child, location, fullPath);\n if (childMatches) {\n return [currentMatch, ...childMatches];\n }\n }\n \n // If no children matched, but we matched exactly this layout route?\n // E.g. /users matches /users layout, and maybe it renders index?\n if (matchedPath === location) {\n // Check for index route\n const indexRoute = route.children.find(c => c.index);\n if (indexRoute) {\n return [currentMatch, { route: indexRoute, params: {}, pathname: matchedPath }];\n }\n // Just the layout? Maybe.\n return [currentMatch];\n }\n }\n \n return null;\n}\n\nfunction compilePath(path: string, prefix: boolean): RegExp {\n // Simple regex conversion\n const paramNames: string[] = [];\n let regexPath = path.replace(/:([^/]+)/g, (_, paramName) => {\n paramNames.push(paramName);\n return '([^/]+)';\n });\n \n // If path is exactly \"/\", and we want prefix matching, it should match everything\n if (regexPath === '/' && prefix) {\n return new RegExp('^');\n }\n \n // If prefix matching allowed, ensure we match segment boundary\n return new RegExp(`^${regexPath}${prefix ? '(?:/|$)' : '$'}`);\n}\n\nfunction extractParams(path: string, values: string[]): Record<string, string> {\n const params: Record<string, string> = {};\n let i = 0;\n // Re-parse to find param names... inefficient but works\n path.replace(/:([^/]+)/g, (_, paramName) => {\n params[paramName] = values[i++];\n return '';\n });\n return params;\n}\n","import { createContext } from '../core/context';\nimport { RouterContext } from './types';\n\n// Global Router Context\nexport const RouterCtx = createContext<RouterContext | null>(null);\n\n// Current Route Depth Context (for Outlet)\n// Stores the index of the current match in the `matches` array\nexport const RouteDepthCtx = createContext<number>(0);\n","import { computed } from '../core/signal';\nimport { createLocation } from './core';\nimport { createRoutesFromChildren, matchRoutes } from './utils';\nimport { LinkProps, RouteProps, RouterContext } from './types';\nimport { h } from '../renderers/dom/h';\nimport { RouterCtx, RouteDepthCtx } from './context';\nimport { useContext } from '../core/context';\n\n// Helper to use Router Context\nexport function useRouter(): RouterContext {\n const ctx = useContext(RouterCtx);\n if (!ctx) {\n throw new Error('useRouter must be used within a <Router> component');\n }\n return ctx;\n}\n\nexport function Router(props: { children: any }) {\n const [location, navigate] = createLocation();\n \n // Parse route configuration from children\n const routes = createRoutesFromChildren(props.children);\n \n // Compute matches\n const matches = computed(() => {\n const loc = location();\n return matchRoutes(routes, loc.pathname) || [];\n });\n \n const params = computed(() => {\n const m = matches();\n if (m.length > 0) {\n // Merge params from all matches? Usually leaf params are most important.\n // Or combine them.\n return m[m.length - 1].params;\n }\n return {};\n });\n\n const routerContext: RouterContext = {\n location,\n params,\n navigate,\n matches\n };\n\n // Provide Context\n // We use a manual Provider wrapper because `Router` returns the root component\n // But Flexium context is stack-based.\n // We can wrap the result in a Provider component if we had one.\n // Or `mountReactive` supports context via `pushProvider` if the component has `_contextId`.\n // But `createContext` returns an object with `Provider` component.\n \n return () => {\n const ms = matches();\n // console.log('Router render, matches:', ms.length);\n \n // No match? Render nothing or 404?\n // Ideally user provides a \"*\" route.\n if (ms.length === 0) return null;\n \n const rootMatch = ms[0];\n const RootComponent = rootMatch.route.component;\n \n // We need to provide RouterCtx AND RouteDepthCtx (0 + 1 = 1 for next outlet)\n // Wait, Outlet at depth 0 should render match[0]?\n // No, Router renders match[0] (Root Layout).\n // Root Layout contains Outlet. Outlet renders match[1].\n // So Outlet needs depth 1.\n \n return h(RouterCtx.Provider, { value: routerContext }, [\n h(RouteDepthCtx.Provider, { value: 1 }, [\n h(RootComponent, { params: rootMatch.params })\n ])\n ]);\n };\n}\n\n/**\n * Route configuration component.\n * Doesn't render anything directly; used by Router to build the route tree.\n */\nexport function Route(_props: RouteProps) {\n return null; \n}\n\n/**\n * Renders the child route content.\n */\nexport function Outlet() {\n const router = useContext(RouterCtx);\n const depth = useContext(RouteDepthCtx); // Default 0\n \n // Safety check\n if (!router) return null;\n \n return () => {\n const ms = router.matches();\n \n // Check if we have a match at this depth\n if (depth >= ms.length) return null;\n \n const match = ms[depth];\n const Component = match.route.component;\n \n // Render component and provide next depth\n return h(RouteDepthCtx.Provider, { value: depth + 1 }, [\n h(Component, { params: match.params })\n ]);\n };\n}\n\nexport function Link(props: LinkProps) {\n const router = useRouter();\n\n const handleClick = (e: Event) => {\n e.preventDefault();\n router.navigate(props.to);\n };\n\n return h('a', {\n href: props.to,\n class: props.class,\n onclick: handleClick\n }, props.children);\n}\n"]}