;\n}\n\nfunction reactiveProxyHandler() {\n return {\n deleteProperty(target: object, key: string | symbol) {\n const had = Reflect.has(target, key);\n const result = Reflect.deleteProperty(target, key);\n if (had) trigger(target, key);\n return result;\n },\n get(target: object, key: string | symbol) {\n track(target, key);\n return Reflect.get(target, key);\n },\n set(target: object, key: string | symbol, value: unknown) {\n if (target[key] === value) return true;\n let newObj = false;\n\n if (isObject(value) && !isObject(target[key])) {\n newObj = true;\n }\n\n if (Reflect.set(target, key, value)) {\n trigger(target, key);\n }\n\n if (newObj) {\n target[key] = reactive(target[key]);\n }\n\n return true;\n },\n };\n}\n", "/**\n * Tokenizer results.\n */\ninterface LexToken {\n type:\n | \"OPEN\"\n | \"CLOSE\"\n | \"PATTERN\"\n | \"NAME\"\n | \"CHAR\"\n | \"ESCAPED_CHAR\"\n | \"MODIFIER\"\n | \"END\";\n index: number;\n value: string;\n}\n\n/**\n * Tokenize input string.\n */\nfunction lexer(str: string): LexToken[] {\n const tokens: LexToken[] = [];\n let i = 0;\n\n while (i < str.length) {\n const char = str[i];\n\n if (char === \"*\" || char === \"+\" || char === \"?\") {\n tokens.push({ type: \"MODIFIER\", index: i, value: str[i++] });\n continue;\n }\n\n if (char === \"\\\\\") {\n tokens.push({ type: \"ESCAPED_CHAR\", index: i++, value: str[i++] });\n continue;\n }\n\n if (char === \"{\") {\n tokens.push({ type: \"OPEN\", index: i, value: str[i++] });\n continue;\n }\n\n if (char === \"}\") {\n tokens.push({ type: \"CLOSE\", index: i, value: str[i++] });\n continue;\n }\n\n if (char === \":\") {\n let name = \"\";\n let j = i + 1;\n\n while (j < str.length) {\n const code = str.charCodeAt(j);\n\n if (\n // `0-9`\n (code >= 48 && code <= 57) ||\n // `A-Z`\n (code >= 65 && code <= 90) ||\n // `a-z`\n (code >= 97 && code <= 122) ||\n // `_`\n code === 95\n ) {\n name += str[j++];\n continue;\n }\n\n break;\n }\n\n if (!name) throw new TypeError(`Missing parameter name at ${i}`);\n\n tokens.push({ type: \"NAME\", index: i, value: name });\n i = j;\n continue;\n }\n\n if (char === \"(\") {\n let count = 1;\n let pattern = \"\";\n let j = i + 1;\n\n if (str[j] === \"?\") {\n throw new TypeError(`Pattern cannot start with \"?\" at ${j}`);\n }\n\n while (j < str.length) {\n if (str[j] === \"\\\\\") {\n pattern += str[j++] + str[j++];\n continue;\n }\n\n if (str[j] === \")\") {\n count--;\n if (count === 0) {\n j++;\n break;\n }\n } else if (str[j] === \"(\") {\n count++;\n if (str[j + 1] !== \"?\") {\n throw new TypeError(`Capturing groups are not allowed at ${j}`);\n }\n }\n\n pattern += str[j++];\n }\n\n if (count) throw new TypeError(`Unbalanced pattern at ${i}`);\n if (!pattern) throw new TypeError(`Missing pattern at ${i}`);\n\n tokens.push({ type: \"PATTERN\", index: i, value: pattern });\n i = j;\n continue;\n }\n\n tokens.push({ type: \"CHAR\", index: i, value: str[i++] });\n }\n\n tokens.push({ type: \"END\", index: i, value: \"\" });\n\n return tokens;\n}\n\nexport interface ParseOptions {\n /**\n * Set the default delimiter for repeat parameters. (default: `'/'`)\n */\n delimiter?: string;\n /**\n * List of characters to automatically consider prefixes when parsing.\n */\n prefixes?: string;\n}\n\n/**\n * Parse a string for the raw tokens.\n */\nexport function parse(str: string, options: ParseOptions = {}): Token[] {\n const tokens = lexer(str);\n const { prefixes = \"./\" } = options;\n const defaultPattern = `[^${escapeString(options.delimiter || \"/#?\")}]+?`;\n const result: Token[] = [];\n let key = 0;\n let i = 0;\n let path = \"\";\n\n const tryConsume = (type: LexToken[\"type\"]): string | undefined => {\n if (i < tokens.length && tokens[i].type === type) return tokens[i++].value;\n };\n\n const mustConsume = (type: LexToken[\"type\"]): string => {\n const value = tryConsume(type);\n if (value !== undefined) return value;\n const { type: nextType, index } = tokens[i];\n throw new TypeError(`Unexpected ${nextType} at ${index}, expected ${type}`);\n };\n\n const consumeText = (): string => {\n let result = \"\";\n let value: string | undefined;\n // tslint:disable-next-line\n while ((value = tryConsume(\"CHAR\") || tryConsume(\"ESCAPED_CHAR\"))) {\n result += value;\n }\n return result;\n };\n\n while (i < tokens.length) {\n const char = tryConsume(\"CHAR\");\n const name = tryConsume(\"NAME\");\n const pattern = tryConsume(\"PATTERN\");\n\n if (name || pattern) {\n let prefix = char || \"\";\n\n if (prefixes.indexOf(prefix) === -1) {\n path += prefix;\n prefix = \"\";\n }\n\n if (path) {\n result.push(path);\n path = \"\";\n }\n\n result.push({\n name: name || key++,\n prefix,\n suffix: \"\",\n pattern: pattern || defaultPattern,\n modifier: tryConsume(\"MODIFIER\") || \"\"\n });\n continue;\n }\n\n const value = char || tryConsume(\"ESCAPED_CHAR\");\n if (value) {\n path += value;\n continue;\n }\n\n if (path) {\n result.push(path);\n path = \"\";\n }\n\n const open = tryConsume(\"OPEN\");\n if (open) {\n const prefix = consumeText();\n const name = tryConsume(\"NAME\") || \"\";\n const pattern = tryConsume(\"PATTERN\") || \"\";\n const suffix = consumeText();\n\n mustConsume(\"CLOSE\");\n\n result.push({\n name: name || (pattern ? key++ : \"\"),\n pattern: name && !pattern ? defaultPattern : pattern,\n prefix,\n suffix,\n modifier: tryConsume(\"MODIFIER\") || \"\"\n });\n continue;\n }\n\n mustConsume(\"END\");\n }\n\n return result;\n}\n\nexport interface TokensToFunctionOptions {\n /**\n * When `true` the regexp will be case sensitive. (default: `false`)\n */\n sensitive?: boolean;\n /**\n * Function for encoding input strings for output.\n */\n encode?: (value: string, token: Key) => string;\n /**\n * When `false` the function can produce an invalid (unmatched) path. (default: `true`)\n */\n validate?: boolean;\n}\n\n/**\n * Compile a string to a template function for the path.\n */\nexport function compile(\n str: string,\n options?: ParseOptions & TokensToFunctionOptions\n) {\n return tokensToFunction
(parse(str, options), options);\n}\n\nexport type PathFunction
= (data?: P) => string;\n\n/**\n * Expose a method for transforming tokens into the path function.\n */\nexport function tokensToFunction
(\n tokens: Token[],\n options: TokensToFunctionOptions = {}\n): PathFunction
{\n const reFlags = flags(options);\n const { encode = (x: string) => x, validate = true } = options;\n\n // Compile all the tokens into regexps.\n const matches = tokens.map(token => {\n if (typeof token === \"object\") {\n return new RegExp(`^(?:${token.pattern})$`, reFlags);\n }\n });\n\n return (data: Record | null | undefined) => {\n let path = \"\";\n\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n\n if (typeof token === \"string\") {\n path += token;\n continue;\n }\n\n const value = data ? data[token.name] : undefined;\n const optional = token.modifier === \"?\" || token.modifier === \"*\";\n const repeat = token.modifier === \"*\" || token.modifier === \"+\";\n\n if (Array.isArray(value)) {\n if (!repeat) {\n throw new TypeError(\n `Expected \"${token.name}\" to not repeat, but got an array`\n );\n }\n\n if (value.length === 0) {\n if (optional) continue;\n\n throw new TypeError(`Expected \"${token.name}\" to not be empty`);\n }\n\n for (let j = 0; j < value.length; j++) {\n const segment = encode(value[j], token);\n\n if (validate && !(matches[i] as RegExp).test(segment)) {\n throw new TypeError(\n `Expected all \"${token.name}\" to match \"${token.pattern}\", but got \"${segment}\"`\n );\n }\n\n path += token.prefix + segment + token.suffix;\n }\n\n continue;\n }\n\n if (typeof value === \"string\" || typeof value === \"number\") {\n const segment = encode(String(value), token);\n\n if (validate && !(matches[i] as RegExp).test(segment)) {\n throw new TypeError(\n `Expected \"${token.name}\" to match \"${token.pattern}\", but got \"${segment}\"`\n );\n }\n\n path += token.prefix + segment + token.suffix;\n continue;\n }\n\n if (optional) continue;\n\n const typeOfMessage = repeat ? \"an array\" : \"a string\";\n throw new TypeError(`Expected \"${token.name}\" to be ${typeOfMessage}`);\n }\n\n return path;\n };\n}\n\nexport interface RegexpToFunctionOptions {\n /**\n * Function for decoding strings for params.\n */\n decode?: (value: string, token: Key) => string;\n}\n\n/**\n * A match result contains data about the path match.\n */\nexport interface MatchResult {\n path: string;\n index: number;\n params: P;\n}\n\n/**\n * A match is either `false` (no match) or a match result.\n */\nexport type Match
= false | MatchResult
;\n\n/**\n * The match function takes a string and returns whether it matched the path.\n */\nexport type MatchFunction
= (\n path: string\n) => Match
;\n\n/**\n * Create path match function from `path-to-regexp` spec.\n */\nexport function match
(\n str: Path,\n options?: ParseOptions & TokensToRegexpOptions & RegexpToFunctionOptions\n) {\n const keys: Key[] = [];\n const re = pathToRegexp(str, keys, options);\n return regexpToFunction
(re, keys, options);\n}\n\n/**\n * Create a path match function from `path-to-regexp` output.\n */\nexport function regexpToFunction
(\n re: RegExp,\n keys: Key[],\n options: RegexpToFunctionOptions = {}\n): MatchFunction
{\n const { decode = (x: string) => x } = options;\n\n return function(pathname: string) {\n const m = re.exec(pathname);\n if (!m) return false;\n\n const { 0: path, index } = m;\n const params = Object.create(null);\n\n for (let i = 1; i < m.length; i++) {\n // tslint:disable-next-line\n if (m[i] === undefined) continue;\n\n const key = keys[i - 1];\n\n if (key.modifier === \"*\" || key.modifier === \"+\") {\n params[key.name] = m[i].split(key.prefix + key.suffix).map(value => {\n return decode(value, key);\n });\n } else {\n params[key.name] = decode(m[i], key);\n }\n }\n\n return { path, index, params };\n };\n}\n\n/**\n * Escape a regular expression string.\n */\nfunction escapeString(str: string) {\n return str.replace(/([.+*?=^!:${}()[\\]|/\\\\])/g, \"\\\\$1\");\n}\n\n/**\n * Get the flags for a regexp from the options.\n */\nfunction flags(options?: { sensitive?: boolean }) {\n return options && options.sensitive ? \"\" : \"i\";\n}\n\n/**\n * Metadata about a key.\n */\nexport interface Key {\n name: string | number;\n prefix: string;\n suffix: string;\n pattern: string;\n modifier: string;\n}\n\n/**\n * A token is a string (nothing special) or key metadata (capture group).\n */\nexport type Token = string | Key;\n\n/**\n * Pull out keys from a regexp.\n */\nfunction regexpToRegexp(path: RegExp, keys?: Key[]): RegExp {\n if (!keys) return path;\n\n const groupsRegex = /\\((?:\\?<(.*?)>)?(?!\\?)/g;\n\n let index = 0;\n let execResult = groupsRegex.exec(path.source);\n while (execResult) {\n keys.push({\n // Use parenthesized substring match if available, index otherwise\n name: execResult[1] || index++,\n prefix: \"\",\n suffix: \"\",\n modifier: \"\",\n pattern: \"\"\n });\n execResult = groupsRegex.exec(path.source);\n }\n\n return path;\n}\n\n/**\n * Transform an array into a regexp.\n */\nfunction arrayToRegexp(\n paths: Array,\n keys?: Key[],\n options?: TokensToRegexpOptions & ParseOptions\n): RegExp {\n const parts = paths.map(path => pathToRegexp(path, keys, options).source);\n return new RegExp(`(?:${parts.join(\"|\")})`, flags(options));\n}\n\n/**\n * Create a path regexp from string input.\n */\nfunction stringToRegexp(\n path: string,\n keys?: Key[],\n options?: TokensToRegexpOptions & ParseOptions\n) {\n return tokensToRegexp(parse(path, options), keys, options);\n}\n\nexport interface TokensToRegexpOptions {\n /**\n * When `true` the regexp will be case sensitive. (default: `false`)\n */\n sensitive?: boolean;\n /**\n * When `true` the regexp won't allow an optional trailing delimiter to match. (default: `false`)\n */\n strict?: boolean;\n /**\n * When `true` the regexp will match to the end of the string. (default: `true`)\n */\n end?: boolean;\n /**\n * When `true` the regexp will match from the beginning of the string. (default: `true`)\n */\n start?: boolean;\n /**\n * Sets the final character for non-ending optimistic matches. (default: `/`)\n */\n delimiter?: string;\n /**\n * List of characters that can also be \"end\" characters.\n */\n endsWith?: string;\n /**\n * Encode path tokens for use in the `RegExp`.\n */\n encode?: (value: string) => string;\n}\n\n/**\n * Expose a function for taking tokens and returning a RegExp.\n */\nexport function tokensToRegexp(\n tokens: Token[],\n keys?: Key[],\n options: TokensToRegexpOptions = {}\n) {\n const {\n strict = false,\n start = true,\n end = true,\n encode = (x: string) => x\n } = options;\n const endsWith = `[${escapeString(options.endsWith || \"\")}]|$`;\n const delimiter = `[${escapeString(options.delimiter || \"/#?\")}]`;\n let route = start ? \"^\" : \"\";\n\n // Iterate over the tokens and create our regexp string.\n for (const token of tokens) {\n if (typeof token === \"string\") {\n route += escapeString(encode(token));\n } else {\n const prefix = escapeString(encode(token.prefix));\n const suffix = escapeString(encode(token.suffix));\n\n if (token.pattern) {\n if (keys) keys.push(token);\n\n if (prefix || suffix) {\n if (token.modifier === \"+\" || token.modifier === \"*\") {\n const mod = token.modifier === \"*\" ? \"?\" : \"\";\n route += `(?:${prefix}((?:${token.pattern})(?:${suffix}${prefix}(?:${token.pattern}))*)${suffix})${mod}`;\n } else {\n route += `(?:${prefix}(${token.pattern})${suffix})${token.modifier}`;\n }\n } else {\n route += `(${token.pattern})${token.modifier}`;\n }\n } else {\n route += `(?:${prefix}${suffix})${token.modifier}`;\n }\n }\n }\n\n if (end) {\n if (!strict) route += `${delimiter}?`;\n\n route += !options.endsWith ? \"$\" : `(?=${endsWith})`;\n } else {\n const endToken = tokens[tokens.length - 1];\n const isEndDelimited =\n typeof endToken === \"string\"\n ? delimiter.indexOf(endToken[endToken.length - 1]) > -1\n : // tslint:disable-next-line\n endToken === undefined;\n\n if (!strict) {\n route += `(?:${delimiter}(?=${endsWith}))?`;\n }\n\n if (!isEndDelimited) {\n route += `(?=${delimiter}|${endsWith})`;\n }\n }\n\n return new RegExp(route, flags(options));\n}\n\n/**\n * Supported `path-to-regexp` input types.\n */\nexport type Path = string | RegExp | Array;\n\n/**\n * Normalize the given path string, returning a regular expression.\n *\n * An empty array can be passed in for the keys, which will hold the\n * placeholder key descriptions. For example, using `/user/:id`, `keys` will\n * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.\n */\nexport function pathToRegexp(\n path: Path,\n keys?: Key[],\n options?: TokensToRegexpOptions & ParseOptions\n) {\n if (path instanceof RegExp) return regexpToRegexp(path, keys);\n if (Array.isArray(path)) return arrayToRegexp(path, keys, options);\n return stringToRegexp(path, keys, options);\n}\n", "import { isComputed } from \"./computed\";\nimport { isRef } from \"./ref\";\n\nexport function unwrap(value: unknown) {\n if (isRef(value) || isComputed(value)) {\n return value.value;\n }\n\n if (typeof value === \"function\") {\n return value();\n }\n\n return value;\n}\n", "import { pathToRegexp } from \"path-to-regexp\";\nimport { Route, RouteExpression, RouteMatch } from \".\";\nimport { Plugin } from \"..\";\nimport { App, Block, Component, current } from \"../..\";\nimport { reactive } from \"../../reactivity/reactive\";\nimport { unwrap } from \"../../reactivity/unwrap\";\nimport { html } from \"../../util\";\n\nexport const activeRouters = new Set();\n\nexport function getRouter(): RouterPlugin | undefined {\n return current.componentBlock ? [...activeRouters].find((router) => router.app === current.componentBlock.context.app) : undefined;\n}\n\nconst link = {\n template: html``,\n props: { href: { default: \"#\" } },\n main({ href }: { href: string }) {\n const go = (e: Event) => {\n e.preventDefault();\n\n activeRouters.forEach((router) => {\n router.doRouteChange(unwrap(href as unknown) as string);\n });\n };\n\n const classes = reactive({ \"router-link\": true });\n\n return { go, classes, href };\n },\n};\n\nasync function runEnterTransition(enter: () => boolean | Promise): Promise {\n return await enter();\n}\n\nconst canEnterRoute = async (route: Route) => {\n if (route.beforeEnter) {\n return await runEnterTransition(route.beforeEnter);\n }\n return true;\n};\n\nconst maybeRedirectRoute = (route: Route) => {\n if (route.redirectTo) {\n activeRouters.forEach((plugin) => plugin.doRouteChange(route.redirectTo));\n }\n};\n\nexport class RouterPlugin implements Plugin {\n app: App;\n routes: Route[] = [];\n pathExpressions = new Map();\n lastPath = \"/\";\n knownRouterViews = new Map();\n knownRouterViewNames = new Map();\n populatedRouterViews = new Map();\n\n constructor(routes: Route[] = []) {\n this.routes = routes;\n }\n\n use(app: App, ..._: any[]) {\n this.app = app;\n this.app.register(\"router-link\", link);\n\n window.addEventListener(\"popstate\", this.onHistoryEvent.bind(this));\n window.addEventListener(\"pushstate\", this.onHistoryEvent.bind(this));\n window.addEventListener(\"load\", this.onHistoryEvent.bind(this));\n\n for (const route of this.routes) {\n this.cacheRouteExpression(route);\n }\n\n this.lastPath = `${location.pathname}${location.search}`;\n window.history.replaceState({}, \"\", this.lastPath);\n\n activeRouters.add(this);\n }\n\n compile(element: Element) {\n if (element.nodeType === Node.ELEMENT_NODE && element.nodeName === \"ROUTER-VIEW\" && !this.knownRouterViews.has(element) && current.componentBlock) {\n this.knownRouterViews.set(element, current.componentBlock);\n this.knownRouterViewNames.set(element.getAttribute(\"name\")?.trim() || \"\", element);\n }\n }\n\n onHistoryEvent(e: PopStateEvent | Event) {\n e.preventDefault();\n e.stopImmediatePropagation();\n\n // @ts-ignore\n const path = new URL(e.currentTarget.location.href).pathname;\n\n if (e.type === \"load\") {\n window.history.replaceState({}, \"\", this.lastPath);\n } else if (e.type === \"pushstate\") {\n window.history.replaceState({}, \"\", path);\n } else if (e.type === \"popstate\") {\n window.history.replaceState({}, \"\", path);\n }\n\n this.lastPath = path;\n\n const matches = this.getMatchesForURL(path);\n this.applyMatches(matches);\n }\n\n doRouteChange(to: string) {\n window.history.pushState({}, \"\", to);\n const matches = this.getMatchesForURL(`${location.pathname}${location.search}`);\n this.applyMatches(matches);\n }\n\n getMatchesForURL(url: string): RouteMatch[] {\n let matches: RouteMatch[] = [];\n\n const matchRoutes = (routes: Route[], parentPath: string = \"\", previousParents = []): RouteMatch[] => {\n let parents = [];\n\n for (const route of routes) {\n parents.push(route);\n const path = `${parentPath}${route.path}`.replace(/\\/\\//g, \"/\");\n const match = this.getPathMatch(path, url);\n if (match) matches.push({ match, parents: [...previousParents, ...parents] });\n if (route.children?.length) {\n matchRoutes(route.children, path, [...previousParents, ...parents]);\n parents = [];\n }\n }\n\n return matches;\n };\n matches = matchRoutes(this.routes);\n return matches;\n }\n\n /**\n * getRouteExpression takes a path like \"/users/:id\" and returns a regex\n * and an array of params that match the path.\n * \"/users/:id\" => { regex: /^\\/users\\/([^\\/]+)\\?jwt=(\\w)$/, params: [\"id\"], query: [\"jwt\"] }\n */\n getRouteExpression(path: string, route: Route): RouteExpression {\n if (this.pathExpressions.has(path)) return this.pathExpressions.get(path);\n\n const params = [];\n const regex = pathToRegexp(path, params, { strict: false, sensitive: false, end: true });\n const expression = { regex, params, path, route };\n this.pathExpressions.set(path, expression);\n return expression;\n }\n\n /**\n *\n * @param path A path like /foo/bar/:id\n * @param url A url like /foo/bar/1234\n * @returns A RouteExpression if the URL matches the regex cached for @param path, null otherwise.\n */\n getPathMatch(path: string, url: string): RouteExpression | null {\n if (this.pathExpressions.get(path)) {\n const match = this.pathExpressions.get(path).regex.exec(url);\n if (match) {\n return this.pathExpressions.get(path);\n }\n }\n\n return null;\n }\n\n async applyMatches(matches: RouteMatch[] | null) {\n if (!matches) return;\n\n const usedRouterViews = new Set();\n\n const renderRoutes = async (routeChain: Route[], rootNode?: Element) => {\n for (const route of routeChain) {\n if (route.view) {\n const viewNode = this.knownRouterViewNames.get(route.view);\n if (viewNode && (await canEnterAndRenderRoute(viewNode, route))) {\n continue;\n }\n } else if (rootNode && (await canEnterAndRenderRoute(rootNode, route))) {\n continue;\n }\n }\n };\n\n const canEnterAndRenderRoute = async (node: Element, route: Route) => {\n const canEnter = await canEnterRoute(route);\n if (canEnter) {\n renderRouteAtNode(node, route);\n return true;\n } else {\n if (route.componentFallback) {\n renderRouteAtNode(node, route, route.componentFallback);\n } else {\n maybeRedirectRoute(route);\n }\n\n return false;\n }\n };\n\n const renderRouteAtNode = (node: Element, route: Route, component?: Component) => {\n if (!usedRouterViews.has(node) || this.populatedRouterViews.get(node)?.route !== route) {\n const div = document.createElement(\"div\");\n node.replaceChildren(div);\n\n const target = div.parentElement;\n\n const block = new Block({\n element: div,\n component: component ? component : route.component,\n replacementType: \"replaceChildren\",\n parentContext: current.componentBlock.context,\n });\n\n target.replaceChild(block.element, div);\n\n this.populatedRouterViews.set(node, { block, route });\n\n usedRouterViews.add(node);\n }\n };\n\n for (const match of matches) {\n const routeChain = [...match.parents, match.match.route];\n const uniqueRouteChain = routeChain.filter((route, index, self) => index === self.findIndex((r) => r.path === route.path));\n const rootNode = this.knownRouterViewNames.get(\"\") ?? null;\n await renderRoutes(uniqueRouteChain, rootNode);\n }\n\n // Clean up stale views\n for (const node of this.knownRouterViews.keys()) {\n if (!usedRouterViews.has(node) && this.populatedRouterViews.has(node)) {\n const entry = this.populatedRouterViews.get(node);\n if (entry) {\n entry.block.teardown();\n this.populatedRouterViews.delete(node);\n }\n }\n }\n }\n\n cacheRouteExpression(route: Route, parentPath: string = \"\") {\n const path = `${parentPath}${route.path}`.replace(/\\/\\//g, \"/\");\n this.getRouteExpression(path, route);\n if (route.children?.length) {\n route.children.forEach((child) => {\n this.cacheRouteExpression(child, path);\n });\n }\n }\n\n destroy() {\n window.removeEventListener(\"popstate\", this.onHistoryEvent.bind(this));\n window.removeEventListener(\"pushstate\", this.onHistoryEvent.bind(this));\n window.removeEventListener(\"load\", this.onHistoryEvent.bind(this));\n }\n}\n", "import { AttributeDirective } from \"./directives/attribute\";\nimport { EventDirective } from \"./directives/event\";\nimport { _for } from \"./directives/for\";\nimport { _if } from \"./directives/if\";\nimport { InterpolationDirective } from \"./directives/interpolation\";\nimport { ShowDirective } from \"./directives/show\";\nimport { _teleport } from \"./directives/teleport\";\nimport { ValueDirective } from \"./directives/value\";\nimport { Plugin } from \"./plugins\";\nimport { isComputed } from \"./reactivity/computed\";\nimport { effect as _effect } from \"./reactivity/effect\";\nimport { reactive } from \"./reactivity/reactive\";\nimport { isRef } from \"./reactivity/ref\";\nimport { checkAndRemoveAttribute, componentHasPropByName, extractPropName, findSlotNodes, findTemplateNodes, isElement, isEventAttribute, isMirrorProp, isObject, isPropAttribute, isRegularProp, isSpreadProp, isText, Slot, stringToElement, Template, toDisplayString } from \"./util\";\n\nexport * from \"./plugins\";\nexport * from \"./plugins/router\";\n\nexport function provide(key: string, value: unknown) {\n if (!current.componentBlock) {\n console.warn(\"Can't provide: no current component block\");\n }\n\n current.componentBlock.provides.set(key, value);\n}\n\nexport function inject(key: string) {\n if (!current.componentBlock) {\n console.warn(\"Can't inject: no current component block\");\n }\n\n let c = current.componentBlock;\n\n while (c) {\n if (c.provides.has(key)) {\n return c.provides.get(key);\n }\n\n c = c.parentComponentBlock;\n }\n\n return undefined;\n}\n\nexport class App {\n root: Block;\n registry = new Map();\n plugins = new Set();\n\n register(name: string, component: Component) {\n this.registry.set(name, component);\n }\n\n use(plugin: Plugin, ...config: any[]) {\n this.plugins.add(plugin);\n plugin.use(this, ...config);\n }\n\n getComponent(tag: string) {\n return this.registry.get(tag);\n }\n\n mount(component: Component, target: string | HTMLElement = \"body\", props: Record = {}) {\n const root = typeof target === \"string\" ? (document.querySelector(target) as HTMLElement) : target;\n const display = root.style.display;\n root.style.display = \"none\";\n this.root = this._mount(component, root, props);\n root.style.display = display;\n return this.root;\n }\n\n private _mount(component: Component, target: HTMLElement, props: Record) {\n const parentContext = createContext({ app: this });\n\n if (props) {\n parentContext.scope = reactive(props);\n bindContextMethods(parentContext.scope);\n }\n\n parentContext.scope.$isRef = isRef;\n parentContext.scope.$isComputed = isComputed;\n\n const block = new Block({\n element: target,\n parentContext,\n component,\n isRoot: true,\n componentProps: props,\n replacementType: \"replaceChildren\",\n });\n\n return block;\n }\n\n unmount() {\n this.root.teardown();\n }\n}\n\nexport interface Context {\n key?: any;\n app: App;\n scope: Record;\n blocks: Block[];\n effects: Array>;\n effect: typeof _effect;\n slots: Slot[];\n templates: Template[];\n}\n\ninterface CreateContextOptions {\n parentContext?: Context;\n app?: App;\n}\n\nexport function createContext({ parentContext, app }: CreateContextOptions): Context {\n const context: Context = {\n app: app ? app : parentContext && parentContext.app ? parentContext.app : null,\n scope: parentContext ? parentContext.scope : reactive({}),\n // scope: reactive({}),\n blocks: [],\n effects: [],\n slots: [],\n templates: parentContext ? parentContext.templates : [],\n effect: (handler: () => void) => {\n const e = _effect(handler);\n context.effects.push(e);\n return e;\n },\n };\n\n return context;\n}\n\nexport const createScopedContext = (ctx: Context, data = {}): Context => {\n const parentScope = ctx.scope;\n const mergedScope = Object.create(parentScope);\n Object.defineProperties(mergedScope, Object.getOwnPropertyDescriptors(data));\n let proxy: any;\n proxy = reactive(\n new Proxy(mergedScope, {\n set(target, key, val, receiver) {\n // when setting a property that doesn't exist on current scope,\n // do not create it on the current scope and fallback to parent scope.\n if (receiver === proxy && !target.hasOwnProperty(key)) {\n return Reflect.set(parentScope, key, val);\n }\n return Reflect.set(target, key, val, receiver);\n },\n }),\n );\n\n bindContextMethods(proxy);\n\n const out: Context = {\n ...ctx,\n scope: {\n ...ctx.scope,\n ...proxy,\n },\n };\n\n return out;\n};\n\nfunction bindContextMethods(scope: Record) {\n for (const key of Object.keys(scope)) {\n if (typeof scope[key] === \"function\") {\n scope[key] = scope[key].bind(scope);\n }\n }\n}\n\nfunction mergeProps(props: Record, defaultProps: Record) {\n const merged = {};\n\n Object.keys(defaultProps).forEach((defaultProp) => {\n const propValue = props.hasOwnProperty(defaultProp) ? props[defaultProp] : defaultProps[defaultProp]?.default;\n\n merged[defaultProp] = reactive(typeof propValue === \"function\" ? propValue() : propValue);\n });\n\n return merged;\n}\n\nexport interface Component {\n template: string;\n props?: Record;\n main?: (props?: Record) => Record | void;\n}\n\ninterface Current {\n componentBlock?: Block;\n}\n\nexport const current: Current = { componentBlock: undefined };\n\ninterface BlockOptions {\n element: Element;\n isRoot?: boolean;\n replacementType?: \"replace\" | \"replaceChildren\";\n componentProps?: Record;\n allProps?: Record;\n parentContext?: Context;\n component?: Component;\n parentComponentBlock?: Block;\n templates?: Template[];\n}\n\nexport class Block {\n element: Element;\n context: Context;\n parentContext: Context;\n component: Component;\n provides = new Map