@manyducks.co/dolla 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"router.js","names":[],"sources":["../src/router/utils.ts","../src/router/store.ts","../src/router/router.ts","../src/router/index.ts"],"sourcesContent":["import { View } from \"../types.js\";\nimport { assert, isArray, isFunction, isObject, isString, uniqueId } from \"../utils.js\";\nimport type { JourneyStep, Route, RouteLayer, Stringable } from \"./types.js\";\n\nexport interface Match {\n /**\n * The path string that triggered this match.\n */\n path: string;\n\n /**\n * The pattern satisfied by `path`.\n */\n pattern: string;\n\n /**\n * Named params as parsed from `path`.\n */\n params: Record<string, string>;\n\n /**\n * Query params as parsed from `path`.\n */\n query: Record<string, string>;\n\n /**\n * Freeform data you wish to store with this route.\n * Merged `data` from all matched layers are available on the router's `match.meta`.\n */\n meta: Record<any, any>;\n}\n\nexport interface RouteMatch extends Match {\n layers: RouteLayer[];\n redirect?: string | ((match: Match) => string) | ((match: Match) => Promise<string>);\n}\n\nexport type RoutePayload = {\n pattern: string;\n meta: Record<any, any>;\n layers?: RouteLayer[];\n redirect?: string | ((match: Match) => string) | ((match: Match) => Promise<string>);\n};\n\nexport type RouteMatchOptions = {\n willMatch?: (route: RoutePayload) => boolean;\n};\n\n/**\n * Separates a URL path into multiple fragments.\n *\n * @param path - A path string (e.g. `\"/api/users/5\"`)\n * @returns an array of fragments (e.g. `[\"api\", \"users\", \"5\"]`)\n */\nexport function splitPath(path: string): string[] {\n return path\n .split(\"/\")\n .map((f) => f.trim())\n .filter(Boolean);\n}\n\n/**\n * Joins multiple URL path fragments into a single string.\n *\n * @param parts - One or more URL fragments (e.g. `[\"api\", \"users\", 5]`)\n * @returns a joined path (e.g. `\"api/users/5\"`)\n */\n\nexport function joinPath(parts: { toString(): string }[]): string {\n const joined = parts\n .map((p) => p.toString())\n .filter(Boolean)\n .join(\"/\");\n if (!joined) return \"\";\n\n const isAbsolute = joined.startsWith(\"/\");\n const segments = joined.split(\"/\");\n const resolved: string[] = [];\n\n for (const segment of segments) {\n if (segment === \"\" || segment === \".\") continue;\n if (segment === \"..\") {\n // Pop the previous segment unless we're at the root, or already backing up\n if (resolved.length > 0 && resolved[resolved.length - 1] !== \"..\") {\n resolved.pop();\n } else if (!isAbsolute) {\n resolved.push(\"..\");\n }\n } else {\n resolved.push(segment);\n }\n }\n\n let result = resolved.join(\"/\");\n if (isAbsolute) result = \"/\" + result;\n\n return result || (isAbsolute ? \"/\" : \"\");\n}\n\nexport function resolvePath(base: string, part: string | null = null): string {\n if (part == null) {\n part = base;\n base = \"\";\n }\n\n // If the target is absolute, it replaces the base entirely\n if (part.startsWith(\"/\")) return joinPath([part]);\n\n // Otherwise, join them and let joinPath resolve the '.' and '..'\n return joinPath([base, part]);\n}\n\nexport function parseQueryParams(query: string): Record<string, string> {\n return Object.fromEntries(new URLSearchParams(query));\n}\n\nexport function mergeQueryParams(\n previous: Record<string, string>,\n current: Record<string, Stringable>,\n preserve?: boolean | string[],\n) {\n const merged: Record<string, any> = {};\n\n if (preserve === true) {\n Object.assign(merged, previous);\n } else if (isArray(preserve)) {\n for (const key of preserve) {\n if (key in previous) merged[key] = previous[key];\n }\n }\n\n Object.assign(merged, current);\n return new URLSearchParams(merged as Record<string, string>);\n}\n\nexport class RouteNode {\n staticChildren = new Map<string, RouteNode>();\n numericChild: RouteNode | null = null;\n paramChild: RouteNode | null = null;\n wildcardChild: RouteNode | null = null;\n\n // Set if this node represents the end of a valid path\n route?: RoutePayload;\n paramName?: string;\n numericName?: string;\n}\n\nexport function buildRouteTree(routes: Route[]): RouteNode {\n const root = new RouteNode();\n const redirectsToValidate: RoutePayload[] = [];\n\n function insertIntoTree(pattern: string, payload: RoutePayload) {\n const parts = splitPath(pattern);\n let current = root;\n\n for (const part of parts) {\n if (part === \"*\") {\n current = current.wildcardChild ??= new RouteNode();\n } else if (part.charCodeAt(0) === 123) {\n // {\n if (part.charCodeAt(1) === 35) {\n // #\n current = current.numericChild ??= new RouteNode();\n current.numericName = part.slice(2, -1);\n } else {\n current = current.paramChild ??= new RouteNode();\n current.paramName = part.slice(1, -1);\n }\n } else {\n const key = part.toLowerCase();\n let next = current.staticChildren.get(key);\n if (!next) current.staticChildren.set(key, (next = new RouteNode()));\n current = next;\n }\n }\n current.route = payload;\n }\n\n function parse(route: Route, parents: Route[] = [], layers: RouteLayer[] = []) {\n assert(isObject<Route>(route) && isString(route.path), \"Invalid route object\");\n\n const parentPaths = parents.map((p) => p.path);\n const parent = parents.at(-1);\n const meta = parent && route.meta ? { ...parent.meta, ...route.meta } : route.meta || {};\n\n const rawPattern = parentPaths.length ? joinPath([...parentPaths, route.path]) : route.path;\n const patterns = expandOptionalPaths(rawPattern);\n\n if (route.redirect) {\n assert(!route.routes && !route.view, \"Route cannot mix redirect with view/routes\");\n let redirect = route.redirect;\n\n if (isString(redirect)) {\n redirect = resolvePath(joinPath(parentPaths), redirect);\n if (!redirect.startsWith(\"/\")) redirect = \"/\" + redirect;\n }\n\n for (const pattern of patterns) {\n const payload: RoutePayload = { pattern, meta, redirect };\n insertIntoTree(pattern, payload);\n if (isString(redirect)) redirectsToValidate.push(payload);\n }\n\n return;\n }\n\n assert(route.view || route.routes, \"Route must have view, redirect, or routes\");\n\n let view = (route.view || ((props: any) => props.children)) as View<any>;\n if (!isFunction(view) && !(view as any)._lazy) {\n throw new TypeError(`Expected view function for ${route.path}`);\n }\n\n if (route.routes) {\n // For parent nodes, create the layer using the raw pattern and recurse\n const layer: RouteLayer = {\n id: uniqueId(),\n pattern: rawPattern,\n view,\n preload: route.preload,\n errorView: route.errorView,\n };\n for (const subroute of route.routes) parse(subroute, [...parents, route], [...layers, layer]);\n } else {\n // For leaf nodes, register every permutation as a valid endpoint\n for (const pattern of patterns) {\n const layer: RouteLayer = {\n id: uniqueId(),\n pattern, // Use the specific expanded pattern for this layer\n view,\n preload: route.preload,\n errorView: route.errorView,\n };\n insertIntoTree(pattern, { pattern, meta, layers: [...layers, layer] });\n }\n }\n }\n\n for (const route of routes) parse(route);\n\n for (const payload of redirectsToValidate) {\n assert(\n matchRoute(root, payload.redirect as string, { willMatch: (r) => r !== payload }),\n `Dead redirect: ${payload.pattern} -> ${payload.redirect}`,\n );\n }\n\n return root;\n}\n\nexport function matchRoute(rootNode: RouteNode, url: string, options: RouteMatchOptions = {}): RouteMatch | undefined {\n const [path, query] = url.split(\"?\");\n const parts = splitPath(path);\n const paramState: Record<string, string> = {}; // Reused across branches\n\n function search(node: RouteNode, index: number): RouteMatch | undefined {\n // if we've consumed all URL parts\n if (index === parts.length) {\n if (node.route && (!options.willMatch || options.willMatch(node.route))) {\n return {\n path: path || \"/\",\n pattern: node.route.pattern,\n params: { ...paramState },\n query: parseQueryParams(query || \"\"),\n meta: node.route.meta,\n layers: node.route.layers ?? [],\n redirect: node.route.redirect,\n };\n }\n\n // Allow wildcards to match zero remaining segments\n if (node.wildcardChild && node.wildcardChild.route) {\n if (!options.willMatch || options.willMatch(node.wildcardChild.route)) {\n return {\n path: path || \"/\",\n pattern: node.wildcardChild.route.pattern,\n params: { ...paramState, wildcard: \"/\" },\n query: parseQueryParams(query || \"\"),\n meta: node.wildcardChild.route.meta,\n layers: node.wildcardChild.route.layers ?? [],\n redirect: node.wildcardChild.route.redirect,\n };\n }\n }\n\n return undefined;\n }\n\n const part = parts[index];\n const lowerPart = part.toLowerCase();\n\n const staticNode = node.staticChildren.get(lowerPart);\n if (staticNode) {\n const result = search(staticNode, index + 1);\n if (result) return result;\n }\n\n if (node.numericChild && !isNaN(Number(part))) {\n paramState[node.numericChild.numericName!] = part;\n const result = search(node.numericChild, index + 1);\n if (result) return result;\n delete paramState[node.numericChild.numericName!];\n }\n\n if (node.paramChild) {\n paramState[node.paramChild.paramName!] = decodeURIComponent(part);\n const result = search(node.paramChild, index + 1);\n if (result) return result;\n delete paramState[node.paramChild.paramName!];\n }\n\n if (node.wildcardChild && node.wildcardChild.route) {\n if (!options.willMatch || options.willMatch(node.wildcardChild.route)) {\n return {\n path: path || \"/\",\n pattern: node.wildcardChild.route.pattern,\n params: { ...paramState, wildcard: \"/\" + parts.slice(index).map(decodeURIComponent).join(\"/\") },\n query: parseQueryParams(query || \"\"),\n meta: node.wildcardChild.route.meta,\n layers: node.wildcardChild.route.layers ?? [],\n redirect: node.wildcardChild.route.redirect,\n };\n }\n }\n\n return undefined;\n }\n\n return search(rootNode, 0);\n}\n\nexport interface ResolvedRoute {\n match?: RouteMatch;\n journey: JourneyStep[];\n}\n\n/**\n * Takes a URL and finds a match, following redirects.\n */\nexport async function resolveRoute(\n rootNode: RouteNode,\n path: string,\n journey: JourneyStep[] = [],\n): Promise<ResolvedRoute> {\n const match = matchRoute(rootNode, path);\n\n if (!match) return { journey: [...journey, { kind: \"miss\", message: `no match for '${path}'` }] };\n\n if (match.redirect != null) {\n let target = match.redirect;\n\n if (isString(target)) {\n target = replaceParams(target, match.params);\n } else {\n target = await target(match);\n assert(isString(target), \"Redirect function must return a path.\");\n if (!target.startsWith(\"/\")) target = resolvePath(match.path, target);\n }\n\n return resolveRoute(rootNode, target, [\n ...journey,\n { kind: \"redirect\", message: `redirecting '${match.path}' -> '${target}'` },\n ]);\n }\n\n // TODO: Data preload\n\n return { match, journey: [...journey, { kind: \"match\", message: `matched route '${match.path}'` }] };\n}\n\n/**\n * Intercepts links within the root node.\n *\n * This is adapted from https://github.com/choojs/nanohref/blob/master/index.js\n *\n * @param root - Element under which to intercept link clicks\n * @param callback - Function to call when a click event is intercepted\n * @param _window - (optional) Override for global window object\n */\nexport function catchLinks(\n root: Element,\n callback: (href: string, anchor: HTMLAnchorElement) => void,\n _window = window,\n) {\n function handler(e: MouseEvent) {\n if ((e.button && e.button !== 0) || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey || e.defaultPrevented) return;\n\n const anchor = (e.target as Element).closest(\"a\");\n if (!anchor || !root.contains(anchor)) return;\n\n const href = anchor.getAttribute(\"href\");\n if (!href) return;\n\n if (\n _window.location.protocol !== anchor.protocol ||\n _window.location.hostname !== anchor.hostname ||\n _window.location.port !== anchor.port ||\n anchor.hasAttribute(\"data-router-ignore\") ||\n anchor.hasAttribute(\"download\") ||\n anchor.getAttribute(\"target\") === \"_blank\" ||\n /^[\\w-_]+:/.test(href)\n ) {\n return;\n }\n\n e.preventDefault();\n callback(href, anchor);\n }\n\n root.addEventListener(\"click\", handler as any);\n return () => root.removeEventListener(\"click\", handler as any);\n}\n\nexport function expandOptionalPaths(path: string): string[] {\n const parts = splitPath(path);\n const permutations: string[][] = [[]];\n\n for (const part of parts) {\n // Strictly enforces the inside style: {param?} or {#param?}\n const isOptional = part.endsWith(\"?}\");\n const cleanPart = isOptional ? part.replace(\"?\", \"\") : part;\n\n if (isOptional) {\n const withPart = permutations.map((p) => [...p, cleanPart]);\n permutations.push(...withPart);\n } else {\n for (const p of permutations) {\n p.push(cleanPart);\n }\n }\n }\n\n return permutations.map((p) => \"/\" + p.join(\"/\")).map((p) => (p === \"/\" ? p : p.replace(/\\/$/, \"\")));\n}\n\n/**\n * Replace route pattern param placeholders with real matched values.\n */\nexport function replaceParams(path: string, params: Record<string, string | number>) {\n for (const key in params) {\n const value = String(params[key]);\n path = path\n .replace(`{${key}}`, value)\n .replace(`{#${key}}`, value)\n .replace(`{${key}?}`, value) // Handle optional string param\n .replace(`{#${key}?}`, value); // Handle optional numeric param\n }\n\n // Remove any remaining unmatched optional parameters\n path = path.replace(/\\{#?[a-zA-Z0-9_]+\\?\\}/g, \"\");\n\n // Clean up any double slashes created by the removal\n path = path.replace(/\\/+/g, \"/\");\n\n // Strip trailing slash unless the entire path is just \"/\"\n if (path.length > 1 && path.endsWith(\"/\")) {\n path = path.slice(0, -1);\n }\n\n return path;\n}\n\nexport interface HistoryAdapter {\n getPath(): string;\n getSearch(): string;\n getKey(): string;\n getIndex(): number;\n push(url: string): void;\n replace(url: string): void;\n}\n\nexport function createHistoryAdapter(useHash: boolean): HistoryAdapter {\n let currentIndex = window.history.state?.index || 0;\n\n if (window.history.state?.index === undefined) {\n window.history.replaceState({ ...window.history.state, key: Date.now().toString(), index: currentIndex }, \"\");\n }\n\n const getPath = useHash ? () => window.location.hash.slice(1).split(\"?\")[0] || \"/\" : () => window.location.pathname;\n const getSearch = useHash\n ? () => {\n const hash = window.location.hash;\n const searchIndex = hash.indexOf(\"?\");\n return searchIndex !== -1 ? hash.slice(searchIndex) : \"\";\n }\n : () => window.location.search;\n\n const getKey = () => window.history.state?.key || \"root\";\n const getIndex = () => window.history.state?.index || 0;\n\n return {\n getPath,\n getSearch,\n getKey,\n getIndex,\n push: (url) => {\n currentIndex++;\n const prefix = useHash ? \"/#\" : \"\";\n window.history.pushState({ key: uniqueId(), index: currentIndex }, \"\", prefix + url);\n },\n replace: (url) => {\n const prefix = useHash ? \"/#\" : \"\";\n window.history.replaceState({ key: getKey(), index: currentIndex }, \"\", prefix + url);\n },\n };\n}\n","import { compose, getDebug, peek, type Context, type Getter, type Setter } from \"../core\";\nimport type { Router } from \"./types\";\nimport { mergeQueryParams, resolvePath, type HistoryAdapter, type Match } from \"./utils\";\n\nexport interface RouterStoreProps {\n currentMatch: Getter<Match>;\n setCurrentMatch: Setter<Match>;\n progress: Getter<number>;\n history: HistoryAdapter;\n updateRoute: () => void;\n guards: Set<() => boolean | Promise<boolean>>;\n}\n\nexport function RouterStore(\n this: Context,\n { currentMatch, setCurrentMatch, progress, history, updateRoute, guards }: RouterStoreProps,\n): Router {\n this.name = \"dolla:router\";\n const debug = getDebug(this);\n\n async function navigate(path: string, replace: boolean) {\n for (const guard of guards) {\n if (await guard()) return;\n }\n\n debug.info(`🗺️ navigating to '${path}'${replace ? \" (replace)\" : \"\"}`);\n\n const resolved = resolvePath(history.getPath(), path);\n replace ? history.replace(resolved) : history.push(resolved);\n updateRoute();\n }\n\n return {\n path: compose(() => currentMatch().path),\n pattern: compose(() => currentMatch().pattern),\n params: compose(() => currentMatch().params),\n query: compose(() => currentMatch().query),\n meta: compose(() => currentMatch().meta),\n progress: progress,\n\n setQuery(params) {\n const m = peek(currentMatch);\n const merged = mergeQueryParams(m.query, params, true);\n const query = Object.fromEntries(merged);\n\n setCurrentMatch({ ...m, query });\n\n const queryString = merged.size ? \"?\" + merged.toString() : \"\";\n history.replace(m.path + queryString);\n\n return query;\n },\n\n back: (steps = 1) => window.history.go(-steps),\n forward: (steps = 1) => window.history.go(steps),\n\n push: (path) => navigate(path, false),\n replace: (path) => navigate(path, true),\n\n block: (guard) => {\n guards.add(guard);\n return () => guards.delete(guard);\n },\n\n isActive(path: string, exact = false) {\n const target = path === \"/\" ? \"/\" : path.replace(/\\/$/, \"\");\n const targetSlash = target === \"/\" ? \"/\" : target + \"/\";\n\n return compose(() => {\n const current = currentMatch().path;\n const normalized = current === \"/\" ? \"/\" : current.replace(/\\/$/, \"\");\n\n if (exact) return normalized === target;\n\n // Ensure segment boundaries match (prevents /app matching /apple)\n return normalized === target || normalized.startsWith(targetSlash);\n });\n },\n };\n}\n","import { addStore, Context, createContext, onCleanup, onMount } from \"../core/context.js\";\nimport { DollaPlugin, getDebug } from \"../core/index.js\";\nimport { DynamicNode } from \"../core/markup/nodes/dynamic.js\";\nimport { ViewNode } from \"../core/markup/nodes/view.js\";\nimport type { MarkupNode } from \"../core/markup/types.js\";\nimport { addListener, createMarkup } from \"../core/markup/utils.js\";\nimport { batch, peek, createAtom } from \"../core/signals.js\";\nimport { DEBUG, PARENT_ELEMENT } from \"../core/symbols.js\";\nimport type { View } from \"../types.js\";\nimport { assert } from \"../utils.js\";\nimport { RouterStore } from \"./store.js\";\nimport type { ActiveLayer, LazyLoader, LazyView, RouterOptions } from \"./types.js\";\nimport {\n buildRouteTree,\n catchLinks,\n createHistoryAdapter,\n type Match,\n mergeQueryParams,\n replaceParams,\n resolveRoute,\n} from \"./utils.js\";\n\nconst ROUTER_ROOT_SLOT = Symbol();\n\n/**\n * Lazy loads a view when its route is first matched.\n *\n * @example\n * {\n * path: \"/users\",\n * view: lazy(() => import(\"./views/users.js\"))\n * }\n */\nexport function lazy(load: LazyLoader): LazyView {\n return { _lazy: true, load };\n}\n\nexport function createRouterPlugin(options: RouterOptions): DollaPlugin {\n return function (context) {\n if (\"scrollRestoration\" in window.history) {\n window.history.scrollRestoration = \"manual\";\n }\n\n const history = createHistoryAdapter(!!options.hash);\n const scrollCache = new Map<string, number>();\n let currentKey = history.getKey();\n\n const [currentMatch, setCurrentMatch] = createAtom<Match>({\n path: history.getPath(),\n pattern: \"\",\n params: {},\n query: Object.fromEntries(new URLSearchParams(history.getSearch())),\n meta: {},\n });\n\n const [progress, setProgress] = createAtom(0);\n const routeTree = buildRouteTree(options.routes);\n\n const guards = new Set<() => boolean | Promise<boolean>>();\n\n const routerContext = createContext(context);\n routerContext.name = \"dolla:router\";\n\n const console = getDebug(routerContext);\n const [rootSlot, setRootSlot] = createAtom<MarkupNode>();\n\n context[ROUTER_ROOT_SLOT] = rootSlot;\n\n const rootLayer: Partial<ActiveLayer> = {\n context,\n slot: rootSlot,\n setSlot: setRootSlot,\n };\n\n const activeLayers: ActiveLayer[] = [];\n\n /**\n * Run when the location changes. Diffs and mounts new routes and updates\n * the signals accordingly.\n */\n async function updateRoute(href?: string) {\n scrollCache.set(currentKey, window.scrollY);\n\n const path = href ?? history.getPath();\n const { match, journey } = await resolveRoute(routeTree, path);\n\n if (context[DEBUG]) {\n for (let i = 0; i < journey.length; i++) {\n const step = journey[i];\n const tag = `(update ${i + 1}/${journey.length})`;\n if (step.kind === \"match\") {\n console.info(`📍 ${tag} ${step.message}`);\n } else if (step.kind === \"redirect\") {\n console.info(`↩️ ${tag} ${step.message}`);\n } else {\n console.info(`💀 ${tag} ${step.message}`);\n }\n }\n }\n\n if (!match) throw new Error(`Failed to match route '${path}'`);\n\n const { layers, params } = match;\n const targetKeys: string[] = [];\n let branchIndex = 0;\n\n // Compute keys and find out where mounted layers diverge from matched layers\n for (let i = 0; i < layers.length; i++) {\n const key = `${layers[i].id}:${replaceParams(layers[i].pattern, params)}`;\n targetKeys.push(key);\n if (branchIndex === i && activeLayers[i]?.key === key) branchIndex++;\n }\n\n const tasks: Promise<void>[] = [];\n const preloadedData: any[] = []; // Offsets match loop index minus divIndex\n\n // Execute preloads and lazy component fetches\n for (let i = branchIndex; i < layers.length; i++) {\n const layer = layers[i];\n\n if (layer.preload) {\n tasks.push(\n Promise.resolve(layer.preload(match)).then((data) => {\n preloadedData[i - branchIndex] = data;\n }),\n );\n }\n\n const view = layer.view as LazyView;\n if (view._lazy) {\n tasks.push(\n view.load().then((mod) => {\n layer.view = (mod as any).default ?? mod; // Overwrite with loaded module\n }),\n );\n }\n }\n\n let caughtError: Error | null = null;\n let errorIndex = -1;\n\n // Track loading progress if there are async tasks\n if (tasks.length > 0) {\n setProgress(0.1);\n let completed = 0;\n const increment = 0.8 / tasks.length;\n\n tasks.forEach((p) => p.then(() => setProgress(0.1 + ++completed * increment)).catch(() => {}));\n\n try {\n await Promise.all(tasks);\n } catch (error) {\n setProgress(0);\n if (error instanceof RedirectError) return api.replace(error.redirectPath);\n\n caughtError = error instanceof Error ? error : new Error(String(error));\n errorIndex = branchIndex;\n }\n }\n\n // Merge query params and sync URL if redirect occurred\n const query = mergeQueryParams(peek(currentMatch).query, match.query, options.preserveQuery);\n const queryString = query.toString();\n const newUrl = match.path + (queryString ? `?${queryString}` : \"\");\n\n if (newUrl !== history.getPath() + history.getSearch()) {\n history.replace(newUrl);\n }\n\n // Batch state updates and DOM mutations\n batch(() => {\n setCurrentMatch({ ...match, query: Object.fromEntries(query) });\n\n if (branchIndex === layers.length && activeLayers.length === layers.length) return;\n\n // Fast truncate arrays and drop old DOM branches\n if (activeLayers[branchIndex]) {\n activeLayers[branchIndex].node.unmount();\n activeLayers.length = branchIndex;\n }\n\n // Mount new layers\n for (let i = branchIndex; i < layers.length; i++) {\n const layer = layers[i];\n const parent = activeLayers[i - 1] ?? rootLayer;\n const [slot, setSlot] = createAtom<MarkupNode>();\n\n let viewToMount = layer.view as View<any>;\n let propsToPass: any = {\n data: preloadedData[i - branchIndex],\n children: createMarkup(DynamicNode, { args: [slot] }),\n };\n\n // Handle Error Boundaries\n if (caughtError && i === errorIndex) {\n if (!layer.errorView) throw caughtError;\n viewToMount = layer.errorView;\n propsToPass = { error: caughtError };\n }\n\n const node = new ViewNode(parent.context!, viewToMount, propsToPass);\n parent.setSlot(node);\n\n activeLayers.push({\n id: layer.id,\n key: targetKeys[i],\n node,\n context: node.context,\n slot,\n setSlot,\n });\n\n if (caughtError && i === errorIndex) break;\n }\n });\n\n setProgress(0);\n\n requestAnimationFrame(() => {\n window.scrollTo(0, scrollCache.get(history.getKey()) ?? 0);\n currentKey = history.getKey();\n });\n }\n\n const api = addStore(context, RouterStore, {\n currentMatch,\n setCurrentMatch,\n progress,\n history,\n updateRoute,\n guards,\n });\n\n onMount(context, () => {\n let isReverting = false;\n let isReplaying = false;\n let lastIndex = history.getIndex();\n\n const removePop = addListener(window, \"popstate\", async () => {\n // If this popstate is the result of us reverting the URL, ignore it.\n if (isReverting) {\n isReverting = false;\n return;\n }\n\n // If this popstate is the result of us replaying an allowed navigation, accept it.\n if (isReplaying) {\n isReplaying = false;\n lastIndex = history.getIndex();\n updateRoute();\n return;\n }\n\n const newIndex = history.getIndex();\n const delta = lastIndex - newIndex; // Positive if user clicked Back\n\n // If guards exist, revert synchronously first\n if (guards.size > 0) {\n isReverting = true;\n window.history.go(delta); // Restores the URL immediately\n\n // Run guards while the URL is back in its original state\n let blocked = false;\n for (const guard of guards) {\n if (await guard()) {\n blocked = true;\n break;\n }\n }\n\n // If guards passed, replay the intended navigation\n if (!blocked) {\n isReplaying = true;\n window.history.go(-delta);\n }\n return;\n }\n\n // Normal flow (no guards)\n lastIndex = newIndex;\n updateRoute();\n });\n\n // Block tab closure/reload if guards exist\n const removeUnload = addListener(window, \"beforeunload\", (e: BeforeUnloadEvent) => {\n if (guards.size > 0) {\n e.preventDefault();\n e.returnValue = \"\"; // Triggers the native browser warning dialog\n }\n });\n\n const removeClick = catchLinks(context[PARENT_ELEMENT] as Element, api.push);\n\n onCleanup(context, () => {\n removePop();\n removeUnload();\n removeClick();\n });\n\n updateRoute();\n });\n };\n}\n\n/**\n * Displays the router's content.\n */\nexport function Outlet(this: Context) {\n this.name = \"dolla:router\";\n\n const rootSlot = this[ROUTER_ROOT_SLOT];\n assert(rootSlot != null, \"Router plugin not found on root.\");\n\n return new DynamicNode(this, rootSlot);\n}\n\nexport class RedirectError extends Error {\n constructor(public redirectPath: string) {\n super(`Redirecting to ${redirectPath}`);\n this.name = \"RedirectError\";\n }\n}\n","import { Context, getStore } from \"../core\";\nimport { RouterStore } from \"./store\";\n\nexport { createRouterPlugin, lazy, Outlet, RedirectError } from \"./router\";\nexport type { RouterOptions } from \"./types\";\n\nexport function getRouter(context: Context) {\n return getStore(context, RouterStore);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"router.js","names":[],"sources":["../src/router/utils.ts","../src/router/store.ts","../src/router/router.ts","../src/router/index.ts"],"sourcesContent":["import { View } from \"../types.js\";\nimport { assert, isArray, isFunction, isObject, isString, uniqueId } from \"../utils.js\";\nimport type { JourneyStep, Route, RouteLayer, Stringable } from \"./types.js\";\n\nexport interface Match {\n /**\n * The path string that triggered this match.\n */\n path: string;\n\n /**\n * The pattern satisfied by `path`.\n */\n pattern: string;\n\n /**\n * Named params as parsed from `path`.\n */\n params: Record<string, string>;\n\n /**\n * Query params as parsed from `path`.\n */\n query: Record<string, string>;\n\n /**\n * Freeform data you wish to store with this route.\n * Merged `data` from all matched layers are available on the router's `match.meta`.\n */\n meta: Record<any, any>;\n}\n\nexport interface RouteMatch extends Match {\n layers: RouteLayer[];\n redirect?: string | ((match: Match) => string) | ((match: Match) => Promise<string>);\n}\n\nexport type RoutePayload = {\n pattern: string;\n meta: Record<any, any>;\n layers?: RouteLayer[];\n redirect?: string | ((match: Match) => string) | ((match: Match) => Promise<string>);\n};\n\nexport type RouteMatchOptions = {\n willMatch?: (route: RoutePayload) => boolean;\n};\n\n/**\n * Separates a URL path into multiple fragments.\n *\n * @param path - A path string (e.g. `\"/api/users/5\"`)\n * @returns an array of fragments (e.g. `[\"api\", \"users\", \"5\"]`)\n */\nexport function splitPath(path: string): string[] {\n return path\n .split(\"/\")\n .map((f) => f.trim())\n .filter(Boolean);\n}\n\n/**\n * Joins multiple URL path fragments into a single string.\n *\n * @param parts - One or more URL fragments (e.g. `[\"api\", \"users\", 5]`)\n * @returns a joined path (e.g. `\"api/users/5\"`)\n */\n\nexport function joinPath(parts: { toString(): string }[]): string {\n const joined = parts\n .map((p) => p.toString())\n .filter(Boolean)\n .join(\"/\");\n if (!joined) return \"\";\n\n const isAbsolute = joined.startsWith(\"/\");\n const segments = joined.split(\"/\");\n const resolved: string[] = [];\n\n for (const segment of segments) {\n if (segment === \"\" || segment === \".\") continue;\n if (segment === \"..\") {\n // Pop the previous segment unless we're at the root, or already backing up\n if (resolved.length > 0 && resolved[resolved.length - 1] !== \"..\") {\n resolved.pop();\n } else if (!isAbsolute) {\n resolved.push(\"..\");\n }\n } else {\n resolved.push(segment);\n }\n }\n\n let result = resolved.join(\"/\");\n if (isAbsolute) result = \"/\" + result;\n\n return result || (isAbsolute ? \"/\" : \"\");\n}\n\nexport function resolvePath(base: string, part: string | null = null): string {\n if (part == null) {\n part = base;\n base = \"\";\n }\n\n // If the target is absolute, it replaces the base entirely\n if (part.startsWith(\"/\")) return joinPath([part]);\n\n // Otherwise, join them and let joinPath resolve the '.' and '..'\n return joinPath([base, part]);\n}\n\nexport function parseQueryParams(query: string): Record<string, string> {\n return Object.fromEntries(new URLSearchParams(query));\n}\n\nexport function mergeQueryParams(\n previous: Record<string, string>,\n current: Record<string, Stringable>,\n preserve?: boolean | string[],\n) {\n const merged: Record<string, any> = {};\n\n if (preserve === true) {\n Object.assign(merged, previous);\n } else if (isArray(preserve)) {\n for (const key of preserve) {\n if (key in previous) merged[key] = previous[key];\n }\n }\n\n Object.assign(merged, current);\n return new URLSearchParams(merged as Record<string, string>);\n}\n\nexport class RouteNode {\n staticChildren = new Map<string, RouteNode>();\n numericChild: RouteNode | null = null;\n paramChild: RouteNode | null = null;\n wildcardChild: RouteNode | null = null;\n\n // Set if this node represents the end of a valid path\n route?: RoutePayload;\n paramName?: string;\n numericName?: string;\n}\n\nexport function buildRouteTree(routes: Route[]): RouteNode {\n const root = new RouteNode();\n const redirectsToValidate: RoutePayload[] = [];\n\n function insertIntoTree(pattern: string, payload: RoutePayload) {\n const parts = splitPath(pattern);\n let current = root;\n\n for (const part of parts) {\n if (part === \"*\") {\n current = current.wildcardChild ??= new RouteNode();\n } else if (part.charCodeAt(0) === 123) {\n // {\n if (part.charCodeAt(1) === 35) {\n // #\n current = current.numericChild ??= new RouteNode();\n current.numericName = part.slice(2, -1);\n } else {\n current = current.paramChild ??= new RouteNode();\n current.paramName = part.slice(1, -1);\n }\n } else {\n const key = part.toLowerCase();\n let next = current.staticChildren.get(key);\n if (!next) current.staticChildren.set(key, (next = new RouteNode()));\n current = next;\n }\n }\n current.route = payload;\n }\n\n function parse(route: Route, parents: Route[] = [], layers: RouteLayer[] = []) {\n assert(isObject<Route>(route) && isString(route.path), \"Invalid route object\");\n\n const parentPaths = parents.map((p) => p.path);\n const parent = parents.at(-1);\n const meta = parent && route.meta ? { ...parent.meta, ...route.meta } : route.meta || {};\n\n const rawPattern = parentPaths.length ? joinPath([...parentPaths, route.path]) : route.path;\n const patterns = expandOptionalPaths(rawPattern);\n\n if (route.redirect) {\n assert(!route.routes && !route.view, \"Route cannot mix redirect with view/routes\");\n let redirect = route.redirect;\n\n if (isString(redirect)) {\n redirect = resolvePath(joinPath(parentPaths), redirect);\n if (!redirect.startsWith(\"/\")) redirect = \"/\" + redirect;\n }\n\n for (const pattern of patterns) {\n const payload: RoutePayload = { pattern, meta, redirect };\n insertIntoTree(pattern, payload);\n if (isString(redirect)) redirectsToValidate.push(payload);\n }\n\n return;\n }\n\n assert(route.view || route.routes, \"Route must have view, redirect, or routes\");\n\n let view = (route.view || ((props: any) => props.children)) as View<any>;\n if (!isFunction(view) && !(view as any)._lazy) {\n throw new TypeError(`Expected view function for ${route.path}`);\n }\n\n if (route.routes) {\n // For parent nodes, create the layer using the raw pattern and recurse\n const layer: RouteLayer = {\n id: uniqueId(),\n pattern: rawPattern,\n view,\n preload: route.preload,\n errorView: route.errorView,\n };\n for (const subroute of route.routes) parse(subroute, [...parents, route], [...layers, layer]);\n } else {\n // For leaf nodes, register every permutation as a valid endpoint\n for (const pattern of patterns) {\n const layer: RouteLayer = {\n id: uniqueId(),\n pattern, // Use the specific expanded pattern for this layer\n view,\n preload: route.preload,\n errorView: route.errorView,\n };\n insertIntoTree(pattern, { pattern, meta, layers: [...layers, layer] });\n }\n }\n }\n\n for (const route of routes) parse(route);\n\n for (const payload of redirectsToValidate) {\n assert(\n matchRoute(root, payload.redirect as string, { willMatch: (r) => r !== payload }),\n `Dead redirect: ${payload.pattern} -> ${payload.redirect}`,\n );\n }\n\n return root;\n}\n\nexport function matchRoute(rootNode: RouteNode, url: string, options: RouteMatchOptions = {}): RouteMatch | undefined {\n const [path, query] = url.split(\"?\");\n const parts = splitPath(path);\n const paramState: Record<string, string> = {}; // Reused across branches\n\n function search(node: RouteNode, index: number): RouteMatch | undefined {\n // if we've consumed all URL parts\n if (index === parts.length) {\n if (node.route && (!options.willMatch || options.willMatch(node.route))) {\n return {\n path: path || \"/\",\n pattern: node.route.pattern,\n params: { ...paramState },\n query: parseQueryParams(query || \"\"),\n meta: node.route.meta,\n layers: node.route.layers ?? [],\n redirect: node.route.redirect,\n };\n }\n\n // Allow wildcards to match zero remaining segments\n if (node.wildcardChild && node.wildcardChild.route) {\n if (!options.willMatch || options.willMatch(node.wildcardChild.route)) {\n return {\n path: path || \"/\",\n pattern: node.wildcardChild.route.pattern,\n params: { ...paramState, wildcard: \"/\" },\n query: parseQueryParams(query || \"\"),\n meta: node.wildcardChild.route.meta,\n layers: node.wildcardChild.route.layers ?? [],\n redirect: node.wildcardChild.route.redirect,\n };\n }\n }\n\n return undefined;\n }\n\n const part = parts[index];\n const lowerPart = part.toLowerCase();\n\n const staticNode = node.staticChildren.get(lowerPart);\n if (staticNode) {\n const result = search(staticNode, index + 1);\n if (result) return result;\n }\n\n if (node.numericChild && !isNaN(Number(part))) {\n paramState[node.numericChild.numericName!] = part;\n const result = search(node.numericChild, index + 1);\n if (result) return result;\n delete paramState[node.numericChild.numericName!];\n }\n\n if (node.paramChild) {\n paramState[node.paramChild.paramName!] = decodeURIComponent(part);\n const result = search(node.paramChild, index + 1);\n if (result) return result;\n delete paramState[node.paramChild.paramName!];\n }\n\n if (node.wildcardChild && node.wildcardChild.route) {\n if (!options.willMatch || options.willMatch(node.wildcardChild.route)) {\n return {\n path: path || \"/\",\n pattern: node.wildcardChild.route.pattern,\n params: { ...paramState, wildcard: \"/\" + parts.slice(index).map(decodeURIComponent).join(\"/\") },\n query: parseQueryParams(query || \"\"),\n meta: node.wildcardChild.route.meta,\n layers: node.wildcardChild.route.layers ?? [],\n redirect: node.wildcardChild.route.redirect,\n };\n }\n }\n\n return undefined;\n }\n\n return search(rootNode, 0);\n}\n\nexport interface ResolvedRoute {\n match?: RouteMatch;\n journey: JourneyStep[];\n}\n\n/**\n * Takes a URL and finds a match, following redirects.\n */\nexport async function resolveRoute(\n rootNode: RouteNode,\n path: string,\n journey: JourneyStep[] = [],\n): Promise<ResolvedRoute> {\n const match = matchRoute(rootNode, path);\n\n if (!match) return { journey: [...journey, { kind: \"miss\", message: `no match for '${path}'` }] };\n\n if (match.redirect != null) {\n let target = match.redirect;\n\n if (isString(target)) {\n target = replaceParams(target, match.params);\n } else {\n target = await target(match);\n assert(isString(target), \"Redirect function must return a path.\");\n if (!target.startsWith(\"/\")) target = resolvePath(match.path, target);\n }\n\n return resolveRoute(rootNode, target, [\n ...journey,\n { kind: \"redirect\", message: `redirecting '${match.path}' -> '${target}'` },\n ]);\n }\n\n // TODO: Data preload\n\n return { match, journey: [...journey, { kind: \"match\", message: `matched route '${match.path}'` }] };\n}\n\n/**\n * Intercepts links within the root node.\n *\n * This is adapted from https://github.com/choojs/nanohref/blob/master/index.js\n *\n * @param root - Element under which to intercept link clicks\n * @param callback - Function to call when a click event is intercepted\n * @param _window - (optional) Override for global window object\n */\nexport function catchLinks(\n root: Element,\n callback: (href: string, anchor: HTMLAnchorElement) => void,\n _window = window,\n) {\n function handler(e: MouseEvent) {\n if ((e.button && e.button !== 0) || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey || e.defaultPrevented) return;\n\n const anchor = (e.target as Element).closest(\"a\");\n if (!anchor || !root.contains(anchor)) return;\n\n const href = anchor.getAttribute(\"href\");\n if (!href) return;\n\n if (\n _window.location.protocol !== anchor.protocol ||\n _window.location.hostname !== anchor.hostname ||\n _window.location.port !== anchor.port ||\n anchor.hasAttribute(\"data-router-ignore\") ||\n anchor.hasAttribute(\"download\") ||\n anchor.getAttribute(\"target\") === \"_blank\" ||\n /^[\\w-_]+:/.test(href)\n ) {\n return;\n }\n\n e.preventDefault();\n callback(href, anchor);\n }\n\n root.addEventListener(\"click\", handler as any);\n return () => root.removeEventListener(\"click\", handler as any);\n}\n\nexport function expandOptionalPaths(path: string): string[] {\n const parts = splitPath(path);\n const permutations: string[][] = [[]];\n\n for (const part of parts) {\n // Strictly enforces the inside style: {param?} or {#param?}\n const isOptional = part.endsWith(\"?}\");\n const cleanPart = isOptional ? part.replace(\"?\", \"\") : part;\n\n if (isOptional) {\n const withPart = permutations.map((p) => [...p, cleanPart]);\n permutations.push(...withPart);\n } else {\n for (const p of permutations) {\n p.push(cleanPart);\n }\n }\n }\n\n return permutations.map((p) => \"/\" + p.join(\"/\")).map((p) => (p === \"/\" ? p : p.replace(/\\/$/, \"\")));\n}\n\n/**\n * Replace route pattern param placeholders with real matched values.\n */\nexport function replaceParams(path: string, params: Record<string, string | number>) {\n for (const key in params) {\n const value = String(params[key]);\n path = path\n .replace(`{${key}}`, value)\n .replace(`{#${key}}`, value)\n .replace(`{${key}?}`, value) // Handle optional string param\n .replace(`{#${key}?}`, value); // Handle optional numeric param\n }\n\n // Remove any remaining unmatched optional parameters\n path = path.replace(/\\{#?[a-zA-Z0-9_]+\\?\\}/g, \"\");\n\n // Clean up any double slashes created by the removal\n path = path.replace(/\\/+/g, \"/\");\n\n // Strip trailing slash unless the entire path is just \"/\"\n if (path.length > 1 && path.endsWith(\"/\")) {\n path = path.slice(0, -1);\n }\n\n return path;\n}\n\nexport interface HistoryAdapter {\n getPath(): string;\n getSearch(): string;\n getKey(): string;\n getIndex(): number;\n push(url: string): void;\n replace(url: string): void;\n}\n\nexport function createHistoryAdapter(useHash: boolean): HistoryAdapter {\n let currentIndex = window.history.state?.index || 0;\n\n if (window.history.state?.index === undefined) {\n window.history.replaceState({ ...window.history.state, key: Date.now().toString(), index: currentIndex }, \"\");\n }\n\n const getPath = useHash ? () => window.location.hash.slice(1).split(\"?\")[0] || \"/\" : () => window.location.pathname;\n const getSearch = useHash\n ? () => {\n const hash = window.location.hash;\n const searchIndex = hash.indexOf(\"?\");\n return searchIndex !== -1 ? hash.slice(searchIndex) : \"\";\n }\n : () => window.location.search;\n\n const getKey = () => window.history.state?.key || \"root\";\n const getIndex = () => window.history.state?.index || 0;\n\n return {\n getPath,\n getSearch,\n getKey,\n getIndex,\n push: (url) => {\n currentIndex++;\n const prefix = useHash ? \"/#\" : \"\";\n window.history.pushState({ key: uniqueId(), index: currentIndex }, \"\", prefix + url);\n },\n replace: (url) => {\n const prefix = useHash ? \"/#\" : \"\";\n window.history.replaceState({ key: getKey(), index: currentIndex }, \"\", prefix + url);\n },\n };\n}\n","import { compose, getDebug, peek, type Context, type Getter, type Setter } from \"../core\";\nimport type { Router } from \"./types\";\nimport { mergeQueryParams, resolvePath, type HistoryAdapter, type Match } from \"./utils\";\n\nexport interface RouterStoreProps {\n currentMatch: Getter<Match>;\n setCurrentMatch: Setter<Match>;\n progress: Getter<number>;\n history: HistoryAdapter;\n updateRoute: () => void;\n guards: Set<() => boolean | Promise<boolean>>;\n}\n\nexport function RouterStore(\n this: Context,\n { currentMatch, setCurrentMatch, progress, history, updateRoute, guards }: RouterStoreProps,\n): Router {\n this.name = \"dolla:router\";\n const debug = getDebug(this);\n\n async function navigate(path: string, replace: boolean) {\n for (const guard of guards) {\n if (await guard()) return;\n }\n\n debug.info(`🗺️ navigating to '${path}'${replace ? \" (replace)\" : \"\"}`);\n\n const resolved = resolvePath(history.getPath(), path);\n replace ? history.replace(resolved) : history.push(resolved);\n updateRoute();\n }\n\n return {\n path: compose(() => currentMatch().path),\n pattern: compose(() => currentMatch().pattern),\n params: compose(() => currentMatch().params),\n query: compose(() => currentMatch().query),\n meta: compose(() => currentMatch().meta),\n progress: progress,\n\n setQuery(params) {\n const m = peek(currentMatch);\n const merged = mergeQueryParams(m.query, params, true);\n const query = Object.fromEntries(merged);\n\n setCurrentMatch({ ...m, query });\n\n const queryString = merged.size ? \"?\" + merged.toString() : \"\";\n history.replace(m.path + queryString);\n\n return query;\n },\n\n back: (steps = 1) => window.history.go(-steps),\n forward: (steps = 1) => window.history.go(steps),\n\n push: (path) => navigate(path, false),\n replace: (path) => navigate(path, true),\n\n block: (guard) => {\n guards.add(guard);\n return () => guards.delete(guard);\n },\n\n isActive(path: string, exact = false) {\n const target = path === \"/\" ? \"/\" : path.replace(/\\/$/, \"\");\n const targetSlash = target === \"/\" ? \"/\" : target + \"/\";\n\n return compose(() => {\n const current = currentMatch().path;\n const normalized = current === \"/\" ? \"/\" : current.replace(/\\/$/, \"\");\n\n if (exact) return normalized === target;\n\n // Ensure segment boundaries match (prevents /app matching /apple)\n return normalized === target || normalized.startsWith(targetSlash);\n });\n },\n };\n}\n","import { addStore, Context, createContext, onCleanup, onMount } from \"../core/context.js\";\nimport { DollaPlugin, getDebug, getRootElement } from \"../core/index.js\";\nimport { DynamicNode } from \"../core/markup/nodes/dynamic.js\";\nimport { ViewNode } from \"../core/markup/nodes/view.js\";\nimport type { MarkupNode } from \"../core/markup/types.js\";\nimport { addListener, createMarkup } from \"../core/markup/utils.js\";\nimport { batch, peek, createAtom } from \"../core/signals.js\";\nimport { DEBUG } from \"../core/symbols.js\";\nimport type { View } from \"../types.js\";\nimport { assert } from \"../utils.js\";\nimport { RouterStore } from \"./store.js\";\nimport type { ActiveLayer, LazyLoader, LazyView, RouterOptions } from \"./types.js\";\nimport {\n buildRouteTree,\n catchLinks,\n createHistoryAdapter,\n type Match,\n mergeQueryParams,\n replaceParams,\n resolveRoute,\n} from \"./utils.js\";\n\nconst ROUTER_ROOT_SLOT = Symbol();\n\n/**\n * Lazy loads a view when its route is first matched.\n *\n * @example\n * {\n * path: \"/users\",\n * view: lazy(() => import(\"./views/users.js\"))\n * }\n */\nexport function lazy(load: LazyLoader): LazyView {\n return { _lazy: true, load };\n}\n\nexport function createRouterPlugin(options: RouterOptions): DollaPlugin {\n return function (context) {\n if (\"scrollRestoration\" in window.history) {\n window.history.scrollRestoration = \"manual\";\n }\n\n const history = createHistoryAdapter(!!options.hash);\n const scrollCache = new Map<string, number>();\n let currentKey = history.getKey();\n\n const [currentMatch, setCurrentMatch] = createAtom<Match>({\n path: history.getPath(),\n pattern: \"\",\n params: {},\n query: Object.fromEntries(new URLSearchParams(history.getSearch())),\n meta: {},\n });\n\n const [progress, setProgress] = createAtom(0);\n const routeTree = buildRouteTree(options.routes);\n\n const guards = new Set<() => boolean | Promise<boolean>>();\n\n const routerContext = createContext(context);\n routerContext.name = \"dolla:router\";\n\n const console = getDebug(routerContext);\n const [rootSlot, setRootSlot] = createAtom<MarkupNode>();\n\n context[ROUTER_ROOT_SLOT] = rootSlot;\n\n const rootLayer: Partial<ActiveLayer> = {\n context,\n slot: rootSlot,\n setSlot: setRootSlot,\n };\n\n const activeLayers: ActiveLayer[] = [];\n\n /**\n * Run when the location changes. Diffs and mounts new routes and updates\n * the signals accordingly.\n */\n async function updateRoute(href?: string) {\n scrollCache.set(currentKey, window.scrollY);\n\n const path = href ?? history.getPath();\n const { match, journey } = await resolveRoute(routeTree, path);\n\n if (context[DEBUG]) {\n for (let i = 0; i < journey.length; i++) {\n const step = journey[i];\n const tag = `(update ${i + 1}/${journey.length})`;\n if (step.kind === \"match\") {\n console.info(`📍 ${tag} ${step.message}`);\n } else if (step.kind === \"redirect\") {\n console.info(`↩️ ${tag} ${step.message}`);\n } else {\n console.info(`💀 ${tag} ${step.message}`);\n }\n }\n }\n\n if (!match) throw new Error(`Failed to match route '${path}'`);\n\n const { layers, params } = match;\n const targetKeys: string[] = [];\n let branchIndex = 0;\n\n // Compute keys and find out where mounted layers diverge from matched layers\n for (let i = 0; i < layers.length; i++) {\n const key = `${layers[i].id}:${replaceParams(layers[i].pattern, params)}`;\n targetKeys.push(key);\n if (branchIndex === i && activeLayers[i]?.key === key) branchIndex++;\n }\n\n const tasks: Promise<void>[] = [];\n const preloadedData: any[] = []; // Offsets match loop index minus divIndex\n\n // Execute preloads and lazy component fetches\n for (let i = branchIndex; i < layers.length; i++) {\n const layer = layers[i];\n\n if (layer.preload) {\n tasks.push(\n Promise.resolve(layer.preload(match)).then((data) => {\n preloadedData[i - branchIndex] = data;\n }),\n );\n }\n\n const view = layer.view as LazyView;\n if (view._lazy) {\n tasks.push(\n view.load().then((mod) => {\n layer.view = (mod as any).default ?? mod; // Overwrite with loaded module\n }),\n );\n }\n }\n\n let caughtError: Error | null = null;\n let errorIndex = -1;\n\n // Track loading progress if there are async tasks\n if (tasks.length > 0) {\n setProgress(0.1);\n let completed = 0;\n const increment = 0.8 / tasks.length;\n\n tasks.forEach((p) => p.then(() => setProgress(0.1 + ++completed * increment)).catch(() => {}));\n\n try {\n await Promise.all(tasks);\n } catch (error) {\n setProgress(0);\n if (error instanceof RedirectError) return api.replace(error.redirectPath);\n\n caughtError = error instanceof Error ? error : new Error(String(error));\n errorIndex = branchIndex;\n }\n }\n\n // Merge query params and sync URL if redirect occurred\n const query = mergeQueryParams(peek(currentMatch).query, match.query, options.preserveQuery);\n const queryString = query.toString();\n const newUrl = match.path + (queryString ? `?${queryString}` : \"\");\n\n if (newUrl !== history.getPath() + history.getSearch()) {\n history.replace(newUrl);\n }\n\n // Batch state updates and DOM mutations\n batch(() => {\n setCurrentMatch({ ...match, query: Object.fromEntries(query) });\n\n if (branchIndex === layers.length && activeLayers.length === layers.length) return;\n\n // Fast truncate arrays and drop old DOM branches\n if (activeLayers[branchIndex]) {\n activeLayers[branchIndex].node.unmount();\n activeLayers.length = branchIndex;\n }\n\n // Mount new layers\n for (let i = branchIndex; i < layers.length; i++) {\n const layer = layers[i];\n const parent = activeLayers[i - 1] ?? rootLayer;\n const [slot, setSlot] = createAtom<MarkupNode>();\n\n let viewToMount = layer.view as View<any>;\n let propsToPass: any = {\n data: preloadedData[i - branchIndex],\n children: createMarkup(DynamicNode, { args: [slot] }),\n };\n\n // Handle Error Boundaries\n if (caughtError && i === errorIndex) {\n if (!layer.errorView) throw caughtError;\n viewToMount = layer.errorView;\n propsToPass = { error: caughtError };\n }\n\n const node = new ViewNode(parent.context!, viewToMount, propsToPass);\n parent.setSlot(node);\n\n activeLayers.push({\n id: layer.id,\n key: targetKeys[i],\n node,\n context: node.context,\n slot,\n setSlot,\n });\n\n if (caughtError && i === errorIndex) break;\n }\n });\n\n setProgress(0);\n\n requestAnimationFrame(() => {\n window.scrollTo(0, scrollCache.get(history.getKey()) ?? 0);\n currentKey = history.getKey();\n });\n }\n\n const api = addStore(context, RouterStore, {\n currentMatch,\n setCurrentMatch,\n progress,\n history,\n updateRoute,\n guards,\n });\n\n onMount(context, () => {\n let isReverting = false;\n let isReplaying = false;\n let lastIndex = history.getIndex();\n\n const removePop = addListener(window, \"popstate\", async () => {\n // If this popstate is the result of us reverting the URL, ignore it.\n if (isReverting) {\n isReverting = false;\n return;\n }\n\n // If this popstate is the result of us replaying an allowed navigation, accept it.\n if (isReplaying) {\n isReplaying = false;\n lastIndex = history.getIndex();\n updateRoute();\n return;\n }\n\n const newIndex = history.getIndex();\n const delta = lastIndex - newIndex; // Positive if user clicked Back\n\n // If guards exist, revert synchronously first\n if (guards.size > 0) {\n isReverting = true;\n window.history.go(delta); // Restores the URL immediately\n\n // Run guards while the URL is back in its original state\n let blocked = false;\n for (const guard of guards) {\n if (await guard()) {\n blocked = true;\n break;\n }\n }\n\n // If guards passed, replay the intended navigation\n if (!blocked) {\n isReplaying = true;\n window.history.go(-delta);\n }\n return;\n }\n\n // Normal flow (no guards)\n lastIndex = newIndex;\n updateRoute();\n });\n\n // Block tab closure/reload if guards exist\n const removeUnload = addListener(window, \"beforeunload\", (e: BeforeUnloadEvent) => {\n if (guards.size > 0) {\n e.preventDefault();\n e.returnValue = \"\"; // Triggers the native browser warning dialog\n }\n });\n\n const removeClick = catchLinks(getRootElement(context), api.push);\n\n onCleanup(context, () => {\n removePop();\n removeUnload();\n removeClick();\n });\n\n updateRoute();\n });\n };\n}\n\n/**\n * Displays the router's content.\n */\nexport function Outlet(this: Context) {\n this.name = \"dolla:router\";\n\n const rootSlot = this[ROUTER_ROOT_SLOT];\n assert(rootSlot != null, \"Router plugin not found on root.\");\n\n return new DynamicNode(this, rootSlot);\n}\n\nexport class RedirectError extends Error {\n constructor(public redirectPath: string) {\n super(`Redirecting to ${redirectPath}`);\n this.name = \"RedirectError\";\n }\n}\n","import { Context, getStore } from \"../core\";\nimport { RouterStore } from \"./store\";\n\nexport { createRouterPlugin, lazy, Outlet, RedirectError } from \"./router\";\nexport type { RouterOptions } from \"./types\";\n\nexport function getRouter(context: Context) {\n return getStore(context, RouterStore);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manyducks.co/dolla",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "A fast, batteries-included JavaScript framework with a React-inspired API and the power of signals.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/core/index.d.ts",
@@ -1 +0,0 @@
1
- {"version":3,"file":"view-cBN-hn_T.js","names":["#root","#context","#slot","#root","#unsubscribe","#update","#cleanup","#children","#props","#context","#ownContext","#root","#applyProps","#childNodes","#refCleanup","#unsubscribers","#applyStyles","#applyClasses","#attach","#props","#view","#node"],"sources":["../src/core/context.ts","../src/core/markup/types.ts","../src/core/markup/nodes/dom.ts","../src/core/markup/scheduler.ts","../src/core/markup/nodes/dynamic.ts","../src/core/symbols.ts","../src/core/markup/nodes/element.ts","../src/core/markup/utils.ts","../src/core/markup/nodes/view.ts"],"sourcesContent":["import type { Store } from \"../types.js\";\nimport { assert } from \"../utils.js\";\nimport { createEffect } from \"./signals.js\";\n\nexport type LifecycleListener = () => any;\n\ntype ContextState = {\n isMounted: boolean;\n};\n\nexport type ComponentState = ContextState & {\n name: string;\n};\n\nexport type Context<T = Record<string | symbol, any>> = ContextState & T;\n\n/*===================================*\\\n|| Context ||\n\\*===================================*/\n\nconst MOUNT_LISTENERS = Symbol(\"Context.mountListeners\");\nconst CLEANUP_LISTENERS = Symbol(\"Context.cleanupListeners\");\n\nexport function createContext(parent?: Context): Context {\n return Object.assign(Object.create(parent ?? null), { isMounted: false });\n}\n\nexport function mountContext(context: Context) {\n if (context.isMounted) return;\n context.isMounted = true;\n _callListeners(context, MOUNT_LISTENERS);\n}\n\nexport function unmountContext(context: Context) {\n if (!context.isMounted) return;\n context.isMounted = false;\n _callListeners(context, CLEANUP_LISTENERS);\n}\n\nfunction _callListeners(context: Context, key: symbol) {\n if (!Object.hasOwn(context, key)) return;\n for (const callback of context[key]) callback();\n context[key].length = 0;\n}\n\n/*===================================*\\\n|| Lifecycle Hooks ||\n\\*===================================*/\n\nexport function onMount(context: Context, fn: LifecycleListener) {\n if (!Object.hasOwn(context, MOUNT_LISTENERS)) context[MOUNT_LISTENERS] = [fn];\n else context[MOUNT_LISTENERS].push(fn);\n}\n\nexport function onCleanup(context: Context, fn: LifecycleListener) {\n if (!Object.hasOwn(context, CLEANUP_LISTENERS)) context[CLEANUP_LISTENERS] = [fn];\n else context[CLEANUP_LISTENERS].push(fn);\n}\n\nexport function onEffect(context: Context, fn: () => void) {\n if (context.isMounted) {\n onCleanup(context, createEffect(fn));\n } else {\n onMount(context, () => {\n onCleanup(context, createEffect(fn));\n });\n }\n}\n\n/*===================================*\\\n|| Stores ||\n\\*===================================*/\n\nexport const STORE_ID = Symbol(\"Dolla.StoreId\");\n\nexport function addStore<Props, Returns>(\n context: Context,\n store: Store<Props, Returns> & { [STORE_ID]?: symbol },\n ...args: undefined extends Props ? [props?: Props] : [props: Props]\n) {\n // Tag the store function with a unique symbol if it doesn't have one.\n store[STORE_ID] ??= Symbol(store.name);\n\n assert(!Object.hasOwn(context, store[STORE_ID]), \"Store was already provided on this context.\");\n\n // Give the store its own context bound to this lifecycle.\n const storeContext = createContext(context) as Context<ComponentState>;\n onMount(context, () => mountContext(storeContext));\n onCleanup(context, () => unmountContext(storeContext));\n storeContext.name = store.name;\n\n return (context[store[STORE_ID]!] = store.call(storeContext, args[0] as Props, storeContext));\n}\n\nexport function getStore<Returns>(context: Context, store: Store<any, Returns> & { [STORE_ID]?: symbol }): Returns {\n const id = store[STORE_ID];\n const result = id ? context[id] : undefined;\n assert(result != null, `Store '${store.name}' is not provided by this context.`);\n return result;\n}\n","import type { IntrinsicElements, View } from \"../../types.js\";\nimport { Context } from \"../index.js\";\n\n/**\n * Determines the type of the `props` object for any kind of Markup type.\n */\nexport type PropsOf<T extends string | View<any> | (new (...args: any[]) => MarkupNode)> =\n T extends View<infer P>\n ? P\n : T extends new (...args: infer Args) => MarkupNode\n ? { args: Args extends [Context, ...infer Rest] ? Rest : [] }\n : T extends keyof IntrinsicElements\n ? IntrinsicElements[T]\n : any;\n\nexport const IS_MARKUP = Symbol();\nexport const IS_MARKUP_NODE = Symbol();\nexport const IS_MARKUP_NODE_CLASS = Symbol();\n\n/**\n * A set of basic metadata that can be constructed into a `MarkupNode`.\n */\nexport interface Markup<\n Type extends string | View<any> | (new (...args: any[]) => MarkupNode) =\n | string\n | View<any>\n | (new (...args: any[]) => MarkupNode),\n> {\n [IS_MARKUP]: true;\n type: Type;\n props: PropsOf<Type>;\n}\n\nexport interface MountTarget {\n insertBefore(node: Node, child: Node | null): any;\n moveBefore?: (node: Node, child: Node | null) => any;\n appendChild(node: Node): any;\n}\n\n/**\n * A node that can be mounted by the Markup layout engine. Can be extended to create new custom node types.\n *\n * A `MarkupNode` instance can be passed anywhere a `Renderable` is required.\n */\nexport abstract class MarkupNode {\n static [IS_MARKUP_NODE_CLASS] = true;\n\n get [IS_MARKUP_NODE]() {\n return true;\n }\n\n /**\n * Returns a single DOM node to represent this MarkupNode's position in the DOM.\n * Usually the parent element, but it can be an empty Text node used as a marker.\n *\n * It only needs to be defined while the node is mounted, so it can be created in the `mount` function.\n */\n abstract getRoot(): Node | undefined;\n\n /**\n * Returns true while this node is mounted.\n */\n abstract isMounted(): boolean;\n\n /**\n * Mount this node to a `parent` element.\n * If passed, this node will be mounted as the next sibling of `after`.\n */\n abstract mount(parent: MountTarget, after?: Node): void;\n\n /**\n * Unmount this MarkupNode from its parent element.\n *\n * The `skipDOM` option can be passed as an optimization when unmounting a parent node.\n * A value of `true` indicates that no DOM operations need to happen because the parent is already being unmounted.\n *\n * @param skipDOM - No DOM updates will be performed when true. Lifecycle methods will be called regardless.\n */\n abstract unmount(skipDOM?: boolean): void;\n\n /**\n * Moves a node without unmounting and remounting (if the browser supports Element.moveBefore).\n */\n abstract move(parent: MountTarget, after?: Node): void;\n}\n","import { Context } from \"../../context.js\";\nimport { MarkupNode, type MountTarget } from \"../types.js\";\nimport { addChild, moveAfter } from \"../utils.js\";\n\n/**\n * A lightweight MarkupNode wrapper for a plain DOM node.\n */\nexport class DOMNode extends MarkupNode {\n #root: Node;\n\n constructor(_context: Context, node: Node) {\n super();\n this.#root = node;\n }\n\n override getRoot() {\n return this.#root;\n }\n\n override isMounted() {\n return this.#root.parentNode != null;\n }\n\n override mount(parent: MountTarget, after?: Node) {\n addChild(parent, this.#root, after);\n }\n\n override unmount(skipDOM = false) {\n if (skipDOM) return;\n this.#root.parentNode?.removeChild(this.#root);\n }\n\n override move(parent: Element, after?: Node) {\n moveAfter(parent, this.#root, after);\n }\n}\n","const pendingUpdates = new Set<() => void>();\nlet isScheduled = false;\n\nfunction flushUpdates() {\n for (const update of pendingUpdates) update();\n pendingUpdates.clear();\n isScheduled = false;\n}\n\nexport function scheduleUpdate(updateFn: () => void) {\n updateFn();\n\n // pendingUpdates.add(updateFn);\n // if (!isScheduled) {\n // isScheduled = true;\n // queueMicrotask(flushUpdates);\n // }\n}\n","import type { Context } from \"../../context.js\";\nimport { subscribe, type Getter } from \"../../signals.js\";\nimport { scheduleUpdate } from \"../scheduler.js\";\nimport { MarkupNode, MountTarget } from \"../types.js\";\nimport { addChild, createTextNode, toMarkupNodes } from \"../utils.js\";\nimport { DOMNode } from \"./dom.js\";\n\n/**\n * Renders any kind of content; markup, signals, DOM nodes, etc.\n * If it can be rendered by Dolla then Dynamic will do it.\n */\n\nexport class DynamicNode extends MarkupNode {\n #root = createTextNode(\"\");\n #children: MarkupNode[] = [];\n #context: Context;\n #slot: Getter<any>;\n #unsubscribe?: () => void;\n\n constructor(context: Context, slot: Getter<any>) {\n super();\n this.#context = context;\n this.#slot = slot;\n }\n\n override getRoot() {\n return this.#root;\n }\n\n override isMounted() {\n return this.#root.parentNode != null;\n }\n\n override mount(parent: MountTarget, after?: Node) {\n if (!this.isMounted()) {\n addChild(parent, this.#root, after);\n this.#unsubscribe = subscribe(this.#slot, (content) => {\n scheduleUpdate(() => {\n this.#update(content);\n });\n });\n }\n }\n\n override unmount(skipDOM = false) {\n this.#unsubscribe?.();\n\n if (this.isMounted()) {\n if (!skipDOM) {\n this.#root.parentNode?.removeChild(this.#root);\n }\n this.#cleanup(skipDOM);\n }\n }\n\n override move(parent: MountTarget, after?: Node) {\n let referenceNode: Node | null = after?.nextSibling ?? null;\n\n if (parent.moveBefore) {\n try {\n parent.moveBefore(this.#root, referenceNode);\n referenceNode = this.#root.nextSibling;\n\n for (let i = 0; i < this.#children.length; i++) {\n const childRoot = this.#children[i].getRoot();\n if (childRoot) {\n (parent as any).moveBefore(childRoot, referenceNode);\n }\n }\n return;\n } catch {\n // Fallthrough to standard insertBefore\n }\n }\n\n // Standard DOM fallback (moves root AND children)\n parent.insertBefore(this.#root, referenceNode);\n referenceNode = this.#root.nextSibling;\n\n for (let i = 0; i < this.#children.length; i++) {\n this.#children[i].move(parent, this.#children[i - 1]?.getRoot() ?? this.#root);\n }\n }\n\n #cleanup(skipDOM: boolean) {\n for (let i = 0; i < this.#children.length; i++) {\n this.#children[i].unmount(skipDOM);\n }\n this.#children.length = 0;\n }\n\n #update(content: any) {\n if (!this.isMounted()) return;\n\n // Fast-path for primitive text updates\n const isPrimitive = typeof content === \"string\" || typeof content === \"number\";\n if (isPrimitive && this.#children.length === 1) {\n const child = this.#children[0];\n if (child instanceof DOMNode) {\n const domNode = child.getRoot();\n if (domNode && domNode.nodeType === Node.TEXT_NODE) {\n domNode.nodeValue = String(content);\n return;\n }\n }\n }\n\n this.#cleanup(false);\n\n if (content == null || content === false) return;\n\n const nodes = toMarkupNodes(this.#context, content);\n\n const parent = this.#root.parentElement!;\n let referenceNode: Node = this.#root;\n\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n node.mount(parent, referenceNode);\n this.#children.push(node);\n\n const nextRoot = node.getRoot();\n if (nextRoot) referenceNode = nextRoot;\n }\n }\n}\n","export const PARENT_ELEMENT = Symbol(\"parentElement\");\nexport const DEBUG = Symbol(\"debug\");\n","import { isArray, isFunction, isNumber, isObject, isString, omit } from \"../../../utils.js\";\nimport { Context, createContext, mountContext, unmountContext } from \"../../context.js\";\nimport { Ref } from \"../../ref.js\";\nimport { type Getter, subscribe } from \"../../signals.js\";\nimport { DEBUG } from \"../../symbols.js\";\nimport { scheduleUpdate } from \"../scheduler.js\";\nimport { MarkupNode, MountTarget } from \"../types.js\";\nimport { addChild, addListener, toMarkupNodes } from \"../utils.js\";\nimport { VIEW, ViewNode } from \"./view.js\";\n\nconst IS_SVG = Symbol(\"isSVG\");\n\n// Properties in this list will not be processed by applyProps because they are already handled elsewhere.\nconst ignoredProps = [\"ref\", \"children\"];\n\n/**\n * Renders an HTML or SVG element.\n */\nexport class ElementNode extends MarkupNode {\n #root: HTMLElement | SVGElement;\n\n readonly #props: Record<string, any>;\n\n #context: Context;\n #ownContext = false;\n #childNodes: MarkupNode[] = [];\n #unsubscribers = new Set<() => void>();\n\n #refCleanup?: () => void;\n\n constructor(context: Context, tag: string, props: Record<string, any>) {\n super();\n\n this.#props = props;\n this.#context = context;\n\n if (tag === \"svg\") {\n // This and all nested views will be created as SVG elements.\n this.#context = createContext(context);\n this.#context[IS_SVG] = true;\n this.#ownContext = true;\n } else if (this.#context[IS_SVG] && tag === \"foreignObject\") {\n // No longer in SVG.\n this.#context = createContext(context);\n this.#context[IS_SVG] = false;\n this.#ownContext = false;\n }\n\n // Create node with the appropriate constructor.\n if (this.#context[IS_SVG]) {\n this.#root = document.createElementNS(\"http://www.w3.org/2000/svg\", tag);\n } else {\n this.#root = document.createElement(tag);\n }\n\n // Add view name as a data attribute debug mode.\n if (this.#context[DEBUG]) {\n const view = this.#context[VIEW] as ViewNode<any>;\n if (view) {\n this.#root.dataset.view = view.context.name;\n }\n }\n }\n\n override getRoot() {\n return this.#root;\n }\n\n override isMounted() {\n return this.#root.parentNode != null;\n }\n\n override mount(parent: MountTarget, after?: Node) {\n const wasMounted = this.isMounted();\n\n if (!wasMounted) {\n this.#applyProps(this.#root, omit(ignoredProps, this.#props));\n\n if (this.#props.children) {\n this.#childNodes = toMarkupNodes(this.#context, this.#props.children);\n for (const child of this.#childNodes) {\n child.mount(this.#root);\n }\n }\n }\n\n const targetSibling = after?.nextSibling ?? null;\n if (this.#root.parentNode !== parent || this.#root.nextSibling !== targetSibling) {\n addChild(parent, this.#root, targetSibling);\n }\n\n if (!wasMounted) {\n if (isFunction<Ref<any>>(this.#props.ref)) {\n const result = this.#props.ref(this.#root);\n if (isFunction(result)) {\n this.#refCleanup = result;\n }\n }\n\n if (this.#ownContext) mountContext(this.#context);\n }\n }\n\n override unmount(skipDOM = false) {\n if (!skipDOM && this.#root.parentNode) {\n this.#root.parentNode.removeChild(this.#root);\n }\n\n for (const child of this.#childNodes) {\n child.unmount(true); // Skip DOM removal for children\n }\n\n // Clear reactivity\n this.#unsubscribers.forEach((unsubscribe) => unsubscribe());\n this.#unsubscribers.clear();\n\n if (this.#ownContext) unmountContext(this.#context);\n\n // Clear ref\n if (this.#refCleanup) {\n this.#refCleanup();\n this.#refCleanup = undefined;\n }\n\n // Release memory\n this.#childNodes.length = 0;\n }\n\n override move(parent: MountTarget, after?: Node) {\n if (parent.moveBefore) {\n try {\n parent.moveBefore(this.#root, after?.nextSibling ?? null);\n return;\n } catch {}\n }\n this.mount(parent, after);\n }\n\n #attach<T>(value: Getter<T> | T, callback: (value: T) => void) {\n if (isFunction<Getter<T>>(value)) {\n this.#unsubscribers.add(\n subscribe(value, (current) => {\n scheduleUpdate(() => callback(current));\n }),\n );\n } else {\n // No need to schedule since DOM node is not connected yet.\n callback(value);\n }\n }\n\n #applyProps(element: any, props: Record<string, unknown>) {\n for (const key in props) {\n const value = props[key];\n\n if (key === \"style\") {\n this.#applyStyles(element, value);\n } else if (key === \"class\" || key === \"className\") {\n this.#applyClasses(element, value);\n } else if (key === \"for\") {\n this.#attach(value, (current) => {\n element.htmlFor = current;\n });\n } else if (key[0] === \".\" || key.startsWith(\"prop:\")) {\n // Keys starting with `.` or `prop:` are set as props.\n\n const _key = key.substring(5);\n this.#attach(value, (current) => {\n element[_key] = current;\n });\n } else if (key[0] === \":\" || key.startsWith(\"attr:\")) {\n // Keys starting with `:` or `attr:` are set as attributes.\n\n const _key = key.substring(5).toLowerCase();\n this.#attach(value, (current) => {\n setAttribute(element, _key, current);\n });\n } else if (key[0] === \"@\" && isFunction(value)) {\n // Anything that's a function starting with `@` is an event listener.\n\n const eventName = key.substring(1);\n this.#unsubscribers.add(addListener(element, eventName, value));\n } else if (key.startsWith(\"on\") && isFunction(value)) {\n // Anything that's a function starting with `on` is an event listener.\n\n const eventName = key.toLowerCase().slice(2);\n this.#unsubscribers.add(addListener(element, eventName, value));\n } else if (key in element && !this.#context[IS_SVG]) {\n // Set as property if the element has one.\n\n if (typeof element[key] === \"boolean\") {\n this.#attach(value, (current) => {\n const isTrue = Boolean(current);\n element[key] = isTrue;\n setAttribute(element, key, isTrue);\n });\n } else {\n this.#attach(value, (current) => {\n element[key] = current;\n });\n }\n } else {\n // Fall back to attributes.\n\n this.#attach(value, (current) => {\n setAttribute(element, key, current);\n });\n }\n }\n }\n\n #applyStyles(element: HTMLElement | SVGElement, styles: unknown) {\n const localUnsubs = new Set<() => void>();\n\n const apply = (current: unknown) => {\n localUnsubs.forEach((unsub) => {\n unsub();\n this.#unsubscribers.delete(unsub);\n });\n localUnsubs.clear();\n element.style.cssText = \"\";\n\n const mapped = getStyleMap(current);\n for (const [name, { value, priority }] of Object.entries(mapped)) {\n if (isFunction(value)) {\n const unsub = subscribe(value, (v) => {\n if (v) element.style.setProperty(name, asPixelsIfNumber(v), priority);\n else element.style.removeProperty(name);\n });\n this.#unsubscribers.add(unsub);\n localUnsubs.add(unsub);\n } else if (value != null) {\n element.style.setProperty(name, asPixelsIfNumber(value), priority);\n }\n }\n };\n\n if (isFunction(styles)) {\n this.#unsubscribers.add(subscribe(styles, apply));\n } else {\n apply(styles);\n }\n }\n\n #applyClasses(element: HTMLElement | SVGElement, classes: unknown) {\n const localUnsubs = new Set<() => void>();\n\n const apply = (current: unknown) => {\n // Clean up nested subscriptions if the top-level signal emits a new object\n localUnsubs.forEach((unsub) => {\n unsub();\n this.#unsubscribers.delete(unsub);\n });\n localUnsubs.clear();\n setAttribute(element, \"class\", null);\n\n const mapped = getClassMap(current);\n for (const [name, value] of Object.entries(mapped)) {\n if (name === \"undefined\") continue;\n\n if (isFunction(value)) {\n const unsub = subscribe(value, (isActive) => element.classList.toggle(name, !!isActive));\n this.#unsubscribers.add(unsub);\n localUnsubs.add(unsub);\n } else if (value) {\n element.classList.add(name);\n }\n }\n };\n\n if (isFunction(classes)) {\n this.#unsubscribers.add(subscribe(classes, apply));\n } else {\n apply(classes);\n }\n }\n}\n\n/**\n * Parse classes into a single object. Classes can be passed as a string, an object with class keys can boolean values, or an array with a mix of both.\n */\nfunction getClassMap(classes: unknown): Record<string, unknown> {\n if (isString(classes)) return Object.fromEntries(classes.split(\" \").map((c) => [c, true]));\n if (isArray(classes)) return Object.assign({}, ...classes.filter(Boolean).map(getClassMap));\n if (isObject(classes)) return classes as Record<string, unknown>;\n return {};\n}\n\n/**\n * Parse styles into a single object.\n */\nfunction getStyleMap(styles: unknown): Record<string, { value: unknown; priority?: string }> {\n if (isString(styles)) {\n return Object.fromEntries(\n styles\n .split(\";\")\n .filter((s) => s.trim())\n .map((line) => {\n const [key, val] = line.split(\":\");\n return [\n camelToKebab(key.trim()),\n {\n value: val.replace(\"!important\", \"\").trim(),\n priority: val.includes(\"!important\") ? \"important\" : \"\",\n },\n ];\n }),\n );\n }\n if (isArray(styles)) return Object.assign({}, ...styles.filter(Boolean).map(getStyleMap));\n if (isObject(styles)) {\n return Object.fromEntries(\n Object.entries(styles).map(([k, v]) => [k.startsWith(\"--\") ? k : camelToKebab(k), { value: v }]),\n );\n }\n return {};\n}\n\n/**\n * Converts a camelCase string to kebab-case.\n */\nfunction camelToKebab(value: string): string {\n return value.replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? \"-\" : \"\") + $.toLowerCase());\n}\n\nfunction asPixelsIfNumber(value: any): string {\n if (isNumber(value)) {\n return `${value}px`;\n } else {\n return value;\n }\n}\n\nfunction setAttribute(element: Element, name: string, value: any) {\n if (value) {\n element.setAttribute(name, String(value));\n } else {\n element.removeAttribute(name);\n }\n}\n","import type { Renderable, View } from \"../../types.js\";\nimport { isArray, isFunction, isNumber, isString } from \"../../utils.js\";\nimport { Context, createContext } from \"../context.js\";\nimport { DOMNode } from \"./nodes/dom.js\";\nimport { DynamicNode } from \"./nodes/dynamic.js\";\nimport { ElementNode } from \"./nodes/element.js\";\nimport { ViewNode } from \"./nodes/view.js\";\nimport { IS_MARKUP, IS_MARKUP_NODE, IS_MARKUP_NODE_CLASS, Markup, MarkupNode, MountTarget, PropsOf } from \"./types.js\";\n\nexport function createMarkup<Type extends string | View<any> | (new (...args: any[]) => MarkupNode)>(\n type: Type,\n props: PropsOf<Type>,\n): Markup<Type> {\n return {\n [IS_MARKUP]: true,\n type,\n props,\n };\n}\n\nexport function isMarkup<T extends string | View<any> | (new (...args: any[]) => MarkupNode)>(\n value: any,\n): value is Markup<T> {\n return value && value[IS_MARKUP];\n}\n\nexport function isMarkupNode(value: any): value is MarkupNode {\n return value && value[IS_MARKUP_NODE];\n}\n\nexport function isMarkupNodeClass(value: any): value is new (...args: any[]) => MarkupNode {\n return value && value[IS_MARKUP_NODE_CLASS];\n}\n\n/**\n * Takes any `Renderable` value and returns a `MarkupNode` that will display it.\n */\nexport function render(content: Renderable, context = createContext()): MarkupNode {\n const nodes = toMarkupNodes(context, content);\n if (nodes.length === 1) {\n return nodes[0]; // if it's just one item return it\n }\n // otherwise wrap it in something that can display multiple nodes\n return new DynamicNode(context, () => nodes);\n}\n\n/**\n * Convert basically anything into an array of `MarkupNode`\n */\nexport function toMarkupNodes(context: Context, ...content: any[]): MarkupNode[] {\n const nodes: MarkupNode[] = [];\n\n // Internal processor to avoid intermediate array allocations\n function process(item: any) {\n if (item == null || item === false) return;\n\n if (isArray(item)) {\n for (let i = 0; i < item.length; i++) {\n process(item[i]);\n }\n } else if (isString(item) || isNumber(item)) {\n nodes.push(new DOMNode(context, createTextNode(String(item))));\n } else if (isMarkup(item)) {\n const { type, props } = item;\n\n if (isMarkupNodeClass(type)) {\n nodes.push(new type(context, ...props.args));\n } else if (isFunction(type)) {\n nodes.push(new ViewNode(context, type as View<any>, props));\n } else if (isString(type)) {\n nodes.push(new ElementNode(context, type, props));\n }\n } else if (isMarkupNode(item)) {\n nodes.push(item);\n } else if (item instanceof Node) {\n nodes.push(new DOMNode(context, item));\n } else if (isFunction(item)) {\n nodes.push(new DynamicNode(context, item));\n }\n }\n\n for (let i = 0; i < content.length; i++) {\n process(content[i]);\n }\n\n return nodes;\n}\n\nexport function addChild(parent: MountTarget, node: Node, after?: Node | null) {\n if (after) {\n parent.insertBefore(node, after?.nextSibling);\n } else {\n parent.appendChild(node);\n }\n}\n\nexport function createTextNode(text: string) {\n return document.createTextNode(text);\n}\n\n/**\n * Moves an element using `moveBefore` if the browser supports it, otherwise falls back to `insertBefore`.\n */\nexport function moveAfter(parent: MountTarget, node: Node, after?: Node | null) {\n const before = after?.nextSibling ?? null;\n if (parent.moveBefore) {\n try {\n parent.moveBefore(node, before);\n return;\n } catch {}\n }\n parent.insertBefore(node, before);\n}\n\nexport function addListener<T extends Event>(target: EventTarget, event: string, listener: (event: T) => any) {\n target.addEventListener(event, listener as any);\n return () => target.removeEventListener(event, listener as any);\n}\n","import type { View } from \"../../../types.js\";\nimport { ComponentState, Context, createContext, mountContext, unmountContext } from \"../../context.js\";\nimport { peek } from \"../../signals.js\";\nimport { MarkupNode } from \"../types.js\";\nimport { createTextNode, render } from \"../utils.js\";\nimport { DOMNode } from \"./dom.js\";\n\nexport const VIEW = Symbol.for(\"ViewNode\");\n\n/**\n * Renders a View.\n */\nexport class ViewNode<P> extends MarkupNode {\n readonly #props: P;\n readonly #view: View<P>;\n #node?: MarkupNode;\n\n readonly context: Context<ComponentState & Record<string | symbol, any>>;\n\n constructor(context: Context, view: View<P>, props: P) {\n super();\n this.context = createContext(context) as Context<ComponentState>;\n this.context[VIEW] = this;\n this.context.name = view.name;\n this.#props = props;\n this.#view = view;\n }\n\n getRoot() {\n return this.#node?.getRoot();\n }\n\n isMounted() {\n return this.context.isMounted;\n }\n\n mount(parent: Element, after?: Node) {\n const wasMounted = this.isMounted();\n\n if (!wasMounted) {\n const viewContent = peek(() => this.#view.call(this.context, this.#props, this.context));\n\n if (viewContent != null && viewContent !== false) {\n this.#node = render(viewContent, this.context);\n } else {\n this.#node = new DOMNode(this.context, createTextNode(\"\"));\n }\n }\n\n this.#node!.mount(parent, after);\n\n if (!wasMounted) mountContext(this.context);\n }\n\n unmount(skipDOM = false) {\n this.#node?.unmount(skipDOM);\n unmountContext(this.context);\n }\n\n move(parent: Element, after?: Node) {\n this.#node?.move(parent, after);\n }\n}\n"],"mappings":"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"}