diff --git a/.gitignore b/.gitignore index 3c3629e..2f984cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +.aider* diff --git a/public/demo.js b/public/demo.js index 0986f57..0cf85cc 100755 --- a/public/demo.js +++ b/public/demo.js @@ -412,11 +412,12 @@ function _if(el, exp, ctx, component, componentProps, allProps) { let block; let activeBranchIndex = -1; const removeActiveBlock = () => { - if (block) { - parent.insertBefore(anchor, block.element); - block.remove(); - block = void 0; + if (!block) { + return; } + parent.insertBefore(anchor, block.element); + block.remove(); + block = void 0; }; ctx.effect(() => { for (let i = 0; i < branches.length; i++) { @@ -730,18 +731,6 @@ var $computed = Symbol("computed"); function isComputed(value) { return isObject(value) && value[$computed]; } -function computed(getter) { - const ref2 = { - get value() { - return getter(); - }, - [$computed]: true - }; - effect(() => { - getter(); - }); - return ref2; -} // src/reactivity/ref.ts var $ref = Symbol("ref"); @@ -859,7 +848,7 @@ var App2 = class { } mount(component, target = "body", props = {}) { const root = typeof target === "string" ? document.querySelector(target) : target; - const display = root.style.display; + const { display } = root.style; root.style.display = "none"; this._mount(component, root, props); root.style.display = display; @@ -873,7 +862,7 @@ var App2 = class { } parentContext.scope.$isRef = isRef; parentContext.scope.$isComputed = isComputed; - const block = new Block({ + return new Block({ app: this, element: target, parentContext, @@ -882,7 +871,6 @@ var App2 = class { componentProps: props, replacementType: "replaceChildren" }); - return block; } unmount() { this.root.teardown(); @@ -890,7 +878,7 @@ var App2 = class { }; function createContext({ parentContext, app: app2 }) { const context = { - app: app2 ? app2 : parentContext && parentContext.app ? parentContext.app : null, + app: app2 ? app2 : parentContext?.app ? parentContext.app : null, scope: parentContext ? parentContext.scope : reactive({}), blocks: [], effects: [], @@ -944,7 +932,7 @@ function mergeProps(props, defaultProps) { }); return merged; } -var current = { componentBlock: void 0 }; +var current2 = { componentBlock: void 0 }; var Block = class { element; context; @@ -962,17 +950,15 @@ var Block = class { this.isFragment = opts.element instanceof HTMLTemplateElement; this.parentComponentBlock = opts.parentComponentBlock; if (opts.component) { - current.componentBlock = this; + current2.componentBlock = this; this.element = stringToElement(opts.component.template); + } else if (this.isFragment) { + this.element = opts.element.content.cloneNode(true); + } else if (typeof opts.element === "string") { + this.element = stringToElement(opts.element); } else { - if (this.isFragment) { - this.element = opts.element.content.cloneNode(true); - } else if (typeof opts.element === "string") { - this.element = stringToElement(opts.element); - } else { - this.element = opts.element.cloneNode(true); - opts.element.replaceWith(this.element); - } + this.element = opts.element.cloneNode(true); + opts.element.replaceWith(this.element); } if (opts.isRoot) { this.context = opts.parentContext; @@ -1026,10 +1012,8 @@ var Block = class { if (opts.element instanceof HTMLElement) { opts.element.replaceWith(this.element); } - } else { - if (opts.element instanceof HTMLElement) { - opts.element.replaceChildren(this.element); - } + } else if (opts.element instanceof HTMLElement) { + opts.element.replaceChildren(this.element); } } } @@ -1111,112 +1095,111 @@ function walk(node, context) { new InterpolationDirective({ element: node, context }); return; } - if (isElement(node)) { - let exp; - const handleDirectives = (node2, context2, component, componentProps, allProps) => { - if (warnInvalidDirectives(node2, [":if", ":for"])) return; - if (warnInvalidDirectives(node2, [":for", ":teleport"])) return; - if (warnInvalidDirectives(node2, [":if", ":teleport"])) return; - if (exp = checkAndRemoveAttribute(node2, ":scope")) { - const scope = evalGet(context2.scope, exp, node2); - if (typeof scope === "object") { - Object.assign(context2.scope, scope); - } - } - if (exp = checkAndRemoveAttribute(node2, ":if")) { - return _if(node2, exp, context2, component, componentProps, allProps); - } - if (exp = checkAndRemoveAttribute(node2, ":for")) { - return _for(node2, exp, context2, component, componentProps, allProps); - } - if (exp = checkAndRemoveAttribute(node2, ":teleport")) { - return _teleport(node2, exp, context2, component, componentProps, allProps); - } - if (exp = checkAndRemoveAttribute(node2, ":show")) { - new ShowDirective({ element: node2, context: context2, expression: exp }); - } - if (exp = checkAndRemoveAttribute(node2, ":ref")) { - context2.scope[exp].value = node2; - } - if (exp = checkAndRemoveAttribute(node2, ":value")) { - new ValueDirective({ element: node2, context: context2, expression: exp }); - } - if (exp = checkAndRemoveAttribute(node2, ":html")) { - const htmlExp = exp; - context2.effect(() => { - const result = evalGet(context2.scope, htmlExp, node2); - if (result instanceof Element) { - node2.replaceChildren(); - node2.append(result); - } else { - node2.innerHTML = result; - } - }); - } - if (exp = checkAndRemoveAttribute(node2, ":text")) { - const textExp = exp; - context2.effect(() => { - node2.textContent = toDisplayString(evalGet(context2.scope, textExp, node2)); - }); - } - }; - const processAttributes = (node2, component) => { - return Array.from(node2.attributes).filter((attr) => isSpreadProp(attr.name) || isMirrorProp(attr.name) || isRegularProp(attr.name) && componentHasPropByName(extractPropName(attr.name), component)).map((attr) => ({ - isMirror: isMirrorProp(attr.name), - isSpread: isSpreadProp(attr.name), - isBind: attr.name.includes("bind"), - originalName: attr.name, - extractedName: extractPropName(attr.name), - exp: attr.value, - value: isMirrorProp(attr.name) ? evalGet(context.scope, extractPropName(attr.name), node2) : attr.value ? evalGet(context.scope, attr.value, node2) : void 0 - })); - }; - if (isComponent(node, context)) { - const component = context.app.getComponent(node.tagName.toLowerCase()); - const allProps = processAttributes(node, component); - const componentProps = allProps.reduce((acc, { isSpread, isMirror, extractedName, value }) => { - if (isSpread) { - const spread = evalGet(context.scope, extractedName, node); - if (isObject(spread)) Object.assign(acc, spread); - } else if (isMirror) { - acc[extractedName] = evalGet(context.scope, extractedName, node); - } else { - acc[extractedName] = value; - } - return acc; - }, {}); - const next2 = handleDirectives(node, context, component, componentProps, allProps); - if (next2) return next2; - const templates = findTemplateNodes(node); - return new Block({ - element: node, - app: current.componentBlock.context.app, - // parentContext: context, - component, - replacementType: "replace", - parentComponentBlock: current.componentBlock, - templates, - componentProps, - allProps - }).element; - } - const next = handleDirectives(node, context); - if (next) return next; - Array.from(node.attributes).forEach((attr) => { - if (isPropAttribute(attr.name)) { - new AttributeDirective({ element: node, context, attr }); - } - if (isEventAttribute(attr.name)) { - new EventDirective({ element: node, context, attr }); - } - }); - walkChildren(node, context); + if (!isElement(node)) { + return; } + let exp; + const handleDirectives = (node2, context2, component, componentProps, allProps) => { + if (warnInvalidDirectives(node2, [":if", ":for"])) return; + if (warnInvalidDirectives(node2, [":for", ":teleport"])) return; + if (warnInvalidDirectives(node2, [":if", ":teleport"])) return; + if (exp = checkAndRemoveAttribute(node2, ":scope")) { + const scope = evalGet(context2.scope, exp, node2); + if (typeof scope === "object") { + Object.assign(context2.scope, scope); + } + } + if (exp = checkAndRemoveAttribute(node2, ":if")) { + return _if(node2, exp, context2, component, componentProps, allProps); + } + if (exp = checkAndRemoveAttribute(node2, ":for")) { + return _for(node2, exp, context2, component, componentProps, allProps); + } + if (exp = checkAndRemoveAttribute(node2, ":teleport")) { + return _teleport(node2, exp, context2, component, componentProps, allProps); + } + if (exp = checkAndRemoveAttribute(node2, ":show")) { + new ShowDirective({ element: node2, context: context2, expression: exp }); + } + if (exp = checkAndRemoveAttribute(node2, ":ref")) { + context2.scope[exp].value = node2; + } + if (exp = checkAndRemoveAttribute(node2, ":value")) { + new ValueDirective({ element: node2, context: context2, expression: exp }); + } + if (exp = checkAndRemoveAttribute(node2, ":html")) { + const htmlExp = exp; + context2.effect(() => { + const result = evalGet(context2.scope, htmlExp, node2); + if (result instanceof Element) { + node2.replaceChildren(); + node2.append(result); + } else { + node2.innerHTML = result; + } + }); + } + if (exp = checkAndRemoveAttribute(node2, ":text")) { + const textExp = exp; + context2.effect(() => { + node2.textContent = toDisplayString(evalGet(context2.scope, textExp, node2)); + }); + } + }; + const processAttributes = (node2, component) => Array.from(node2.attributes).filter((attr) => isSpreadProp(attr.name) || isMirrorProp(attr.name) || isRegularProp(attr.name) && componentHasPropByName(extractPropName(attr.name), component)).map((attr) => ({ + isMirror: isMirrorProp(attr.name), + isSpread: isSpreadProp(attr.name), + isBind: attr.name.includes("bind"), + originalName: attr.name, + extractedName: extractPropName(attr.name), + exp: attr.value, + value: isMirrorProp(attr.name) ? evalGet(context.scope, extractPropName(attr.name), node2) : attr.value ? evalGet(context.scope, attr.value, node2) : void 0 + })); + if (isComponent(node, context)) { + const component = context.app.getComponent(node.tagName.toLowerCase()); + const allProps = processAttributes(node, component); + const componentProps = allProps.reduce((acc, { isSpread, isMirror, extractedName, value }) => { + if (isSpread) { + const spread = evalGet(context.scope, extractedName, node); + if (isObject(spread)) Object.assign(acc, spread); + } else if (isMirror) { + acc[extractedName] = evalGet(context.scope, extractedName, node); + } else { + acc[extractedName] = value; + } + return acc; + }, {}); + const next2 = handleDirectives(node, context, component, componentProps, allProps); + if (next2) return next2; + const templates = findTemplateNodes(node); + return new Block({ + element: node, + app: current2.componentBlock.context.app, + // parentContext: context, + component, + replacementType: "replace", + parentComponentBlock: current2.componentBlock, + templates, + componentProps, + allProps + }).element; + } + const next = handleDirectives(node, context); + if (next) return next; + Array.from(node.attributes).forEach((attr) => { + if (isPropAttribute(attr.name)) { + new AttributeDirective({ element: node, context, attr }); + } + if (isEventAttribute(attr.name)) { + new EventDirective({ element: node, context, attr }); + } + }); + walkChildren(node, context); } function walkChildren(node, context) { - let child = node.firstChild; - while (child) { - child = walk(child, context) || child.nextSibling; + let child2 = node.firstChild; + while (child2) { + child2 = walk(child2, context) || child2.nextSibling; } } var evalFuncCache = {}; @@ -1251,38 +1234,41 @@ function flattenRefs(scope) { const mapped = {}; for (const key in scope) { if (scope.hasOwnProperty(key)) { - if (isRef(scope[key])) { - mapped[key] = scope[key].value; - } else { - mapped[key] = scope[key]; - } + mapped[key] = isRef(scope[key]) ? scope[key].value : scope[key]; } } return mapped; } // src/demo.ts -var main = { +var child = { template: html` -
-

phase

-

Colors

- -
-
-
{{variant}}-{{rank}}
-
-
-
+
+ I am child and I have a cheeseburger: "{{food}}" (does not inherit) +
+ +
`, main() { - const ranks = reactive(["5", "10", "20", "30", "40", "50", "60", "70", "80", "90"]); - const basesReverse = computed(() => Array.from(ranks).reverse()); - const bg = (variant, rank, index) => ({ backgroundColor: `var(--${variant}-${rank})`, color: `var(--${variant}-${basesReverse.value[index]})` }); - return { ranks, bg }; + const food = ref("\u{1F354}"); + return { food }; + } +}; +var main = { + template: html` +
+
+
Parent has pizza: {{food}} and scoped drink: {{drink}}
+ Child slot, food: {{food}} {{drink}} +
+
+ `, + main() { + return { food: ref("nothing") }; } }; var app = new App2(); +app.register("child", child); app.mount(main, "#app"); //# sourceMappingURL=demo.js.map diff --git a/public/demo.js.map b/public/demo.js.map index dc9af36..0611be8 100755 --- a/public/demo.js.map +++ b/public/demo.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../src/util.ts", "../src/directives/attribute.ts", "../src/directives/event.ts", "../src/directives/for.ts", "../src/directives/if.ts", "../src/directives/interpolation.ts", "../src/directives/show.ts", "../src/directives/teleport.ts", "../src/directives/value.ts", "../src/reactivity/effect.ts", "../src/reactivity/computed.ts", "../src/reactivity/ref.ts", "../src/reactivity/reactive.ts", "../src/reactivity/unwrap.ts", "../src/plugins/router/plugin.ts", "../src/index.ts", "../src/demo.ts"], - "sourcesContent": ["import { Component } from \".\";\n\nexport function stringToElement(template: string): Element {\n const parser = new DOMParser();\n const doc = parser.parseFromString(template, \"text/html\");\n return doc.body.firstChild as Element;\n}\n\nexport const isText = (node: Node): node is Text => {\n return node.nodeType === Node.TEXT_NODE;\n};\n\nexport const isTemplate = (node: Node): node is HTMLTemplateElement => {\n return node.nodeName === \"TEMPLATE\";\n};\n\nexport const isElement = (node: Node): node is Element => {\n return node.nodeType === Node.ELEMENT_NODE;\n};\n\nexport function isObject(value: any): value is object {\n return value !== null && typeof value === \"object\" && !isArray(value);\n}\n\nexport function isArray(value: any): value is any[] {\n return Array.isArray(value);\n}\n\nexport function checkAndRemoveAttribute(el: Element, attrName: string): string | null {\n // Attempt to get the attribute value\n const attributeValue = el.getAttribute(attrName);\n\n // If attribute exists, remove it from the element\n if (attributeValue !== null) {\n el.removeAttribute(attrName);\n }\n\n // Return the value of the attribute or null if not present\n return attributeValue;\n}\n\nexport interface Slot {\n node: Element;\n name: string;\n}\n\nexport interface Template {\n targetSlotName: string;\n node: HTMLTemplateElement;\n}\n\nexport function findSlotNodes(element: Element): Slot[] {\n const slots: Slot[] = [];\n\n const findSlots = (node: Element) => {\n Array.from(node.childNodes).forEach((node) => {\n if (isElement(node)) {\n if (node.nodeName === \"SLOT\") {\n slots.push({ node, name: node.getAttribute(\"name\") || \"default\" });\n }\n\n if (node.hasChildNodes()) {\n findSlots(node);\n }\n }\n });\n };\n\n findSlots(element);\n\n return slots;\n}\n\nexport function findTemplateNodes(element: Element) {\n const templates: Template[] = [];\n\n const findTemplates = (element: Element) => {\n let defaultContentNodes: Node[] = [];\n\n Array.from(element.childNodes).forEach((node) => {\n if (isElement(node) || isText(node)) {\n if (isElement(node) && node.nodeName === \"TEMPLATE\" && isTemplate(node)) {\n templates.push({ targetSlotName: node.getAttribute(\"slot\") || \"\", node });\n } else {\n // Capture non-template top-level nodes and text nodes for default slot\n defaultContentNodes.push(node);\n }\n }\n });\n\n if (defaultContentNodes.length > 0) {\n // Create a template element with a default slot\n const defaultTemplate = document.createElement(\"template\");\n defaultTemplate.setAttribute(\"slot\", \"default\");\n\n defaultContentNodes.forEach((node) => {\n defaultTemplate.content.appendChild(node);\n });\n\n templates.push({ targetSlotName: \"default\", node: defaultTemplate });\n }\n };\n\n findTemplates(element);\n\n return templates;\n}\n\nexport const nextTick = async (f?: Function) => {\n await new Promise((r) =>\n setTimeout((_) =>\n requestAnimationFrame((_) => {\n f && f();\n r();\n }),\n ),\n );\n};\n\nexport function html(strings: TemplateStringsArray, ...values: any[]): string {\n // List of valid self-closing tags in HTML\n const selfClosingTags = [\"area\", \"base\", \"br\", \"col\", \"embed\", \"hr\", \"img\", \"input\", \"link\", \"meta\", \"param\", \"source\", \"track\", \"wbr\"];\n\n // Join the strings and values into a single template\n let result = strings.reduce((acc, str, i) => acc + str + (values[i] || \"\"), \"\");\n\n // Match non-HTML valid self-closing tags\n result = result.replace(/<([a-zA-Z][^\\s/>]*)\\s*([^>]*?)\\/>/g, (match, tagName, attributes) => {\n // If the tag is a valid self-closing tag, return it as is\n if (selfClosingTags.includes(tagName.toLowerCase())) {\n return match;\n }\n\n // Return the tag as an open/close tag preserving attributes\n return `<${tagName} ${attributes}>`;\n });\n\n return result;\n}\n\nexport function toDisplayString(value: unknown) {\n return value == null ? \"\" : isObject(value) ? JSON.stringify(value, null, 2) : String(value);\n}\n\nexport function insertAfter(newNode: Node, existingNode: Node) {\n if (existingNode.nextSibling) {\n existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling);\n } else {\n existingNode?.parentNode?.appendChild(newNode);\n }\n}\n\nexport function insertBefore(newNode: Node, existingNode: Node) {\n existingNode.parentNode?.insertBefore(newNode, existingNode);\n}\n\nexport function isPropAttribute(attrName: string) {\n if (attrName.startsWith(\".\")) {\n return true;\n }\n\n if (attrName.startsWith(\"{\") && attrName.endsWith(\"}\")) {\n return true;\n }\n\n return false;\n}\n\nexport function isSpreadProp(attr: string) {\n return attr.startsWith(\"...\");\n}\n\nexport function isMirrorProp(attr: string) {\n return attr.startsWith(\"{\") && attr.endsWith(\"}\");\n}\n\nexport function isRegularProp(attr: string) {\n return attr.startsWith(\".\");\n}\n\nexport function isEventAttribute(attrName: string) {\n return attrName.startsWith(\"@\");\n}\n\nexport function componentHasPropByName(name: string, component: Component) {\n return Object.keys(component?.props ?? {}).some((prop) => prop === name);\n}\n\nexport function extractAttributeName(attrName: string) {\n return attrName\n .replace(/^\\.\\.\\./, \"\")\n .replace(/^\\./, \"\")\n .replace(/^{/, \"\")\n .replace(/}$/, \"\")\n .replace(/:bind$/, \"\");\n}\n\nfunction dashToCamel(str: string) {\n return str.toLowerCase().replace(/-([a-z])/g, (g) => g[1].toUpperCase());\n}\n\nexport function extractPropName(attrName: string) {\n return dashToCamel(extractAttributeName(attrName));\n}\n\nexport function classNames(_: any) {\n const classes = [];\n for (let i = 0; i < arguments.length; i++) {\n const arg = arguments[i];\n if (!arg) continue;\n const argType = typeof arg;\n if (argType === \"string\" || argType === \"number\") {\n classes.push(arg);\n } else if (Array.isArray(arg)) {\n if (arg.length) {\n const inner = classNames.apply(null, arg);\n if (inner) {\n classes.push(inner);\n }\n }\n } else if (argType === \"object\") {\n if (arg.toString === Object.prototype.toString) {\n for (let key in arg) {\n if (Object.hasOwnProperty.call(arg, key) && arg[key]) {\n classes.push(key);\n }\n }\n } else {\n classes.push(arg.toString());\n }\n }\n }\n return classes.join(\" \");\n}\n", "import { Context, evalGet } from \"..\";\nimport { classNames, extractAttributeName } from \"../util\";\n\ninterface AttributeDirectiveOptions {\n element: Element;\n context: Context;\n attr: Attr;\n}\n\ninterface Is {\n sameNameProperty: boolean;\n bound: boolean;\n spread: boolean;\n componentProp: boolean;\n}\n\nexport class AttributeDirective {\n element: Element;\n context: Context;\n expression: string;\n attr: Attr;\n extractedAttributeName: string;\n\n previousClasses: string[] = [];\n previousStyles: { [key: string]: string } = {};\n\n is: Is = {\n sameNameProperty: false,\n bound: false,\n spread: false,\n componentProp: false,\n };\n\n constructor({ element, context, attr }: AttributeDirectiveOptions) {\n this.element = element;\n this.context = context;\n this.expression = attr.value;\n this.attr = attr;\n this.extractedAttributeName = extractAttributeName(attr.name);\n\n this.is = {\n sameNameProperty: attr.name.startsWith(\"{\") && attr.name.endsWith(\"}\"),\n bound: attr.name.includes(\":bind\"),\n spread: attr.name.startsWith(\"...\"),\n componentProp: false,\n };\n\n if (this.is.sameNameProperty) {\n this.expression = this.extractedAttributeName;\n }\n\n if (this.is.spread) {\n this.expression = this.extractedAttributeName;\n }\n\n element.removeAttribute(attr.name);\n\n if (this.is.bound) {\n context.effect(this.update.bind(this));\n } else {\n this.update();\n }\n }\n\n update() {\n let value = evalGet(this.context.scope, this.expression, this.element);\n\n if (this.is.spread && typeof value === \"object\") {\n for (const [key, val] of Object.entries(value)) {\n this.element.setAttribute(key, String(val));\n }\n } else if ((typeof value === \"object\" || Array.isArray(value)) && this.extractedAttributeName === \"class\") {\n value = classNames(value);\n const next = value.split(\" \");\n\n // If we now have classes that are not already on the element, add them now.\n // Remove classes that are no longer on the element.\n const diff = next.filter((c: string) => !this.previousClasses.includes(c)).filter(Boolean);\n const rm = this.previousClasses.filter((c) => !next.includes(c));\n\n diff.forEach((c: string) => {\n this.previousClasses.push(c);\n this.element.classList.add(c);\n });\n\n rm.forEach((c) => {\n this.previousClasses = this.previousClasses.filter((addedClass) => addedClass !== c);\n this.element.classList.remove(c);\n });\n } else if (typeof value === \"object\" && this.extractedAttributeName === \"style\") {\n const next = Object.keys(value);\n const rm = Object.keys(this.previousStyles).filter((style) => !next.includes(style));\n\n next.forEach((style) => {\n this.previousStyles[style] = value[style];\n // @ts-ignore\n this.element.style[style] = value[style];\n });\n\n rm.forEach((style) => {\n this.previousStyles[style] = \"\";\n // @ts-ignore\n this.element.style[style] = \"\";\n });\n\n this.previousStyles = value;\n } else {\n this.element.setAttribute(this.extractedAttributeName, value);\n }\n }\n}\n", "import { Context, evalGet } from \"..\";\n\ninterface EventDirectiveOptions {\n element: Element;\n context: Context;\n attr: Attr;\n}\n\nexport class EventDirective {\n element: Element;\n context: Context;\n expression: string;\n attr: Attr;\n eventCount = 0;\n\n constructor({ element, context, attr }: EventDirectiveOptions) {\n this.element = element;\n this.context = context;\n this.expression = attr.value;\n this.attr = attr;\n\n const eventName = attr.name.replace(/^@/, \"\");\n const parts = eventName.split(\".\");\n\n this.element.addEventListener(parts[0], (event) => {\n if (parts.includes(\"prevent\")) event.preventDefault();\n if (parts.includes(\"stop\")) event.stopPropagation();\n if (parts.includes(\"once\") && this.eventCount > 0) return;\n\n this.eventCount++;\n\n const handler = evalGet(context.scope, attr.value, element);\n if (typeof handler === \"function\") {\n handler(event);\n }\n });\n\n element.removeAttribute(attr.name);\n }\n}\n", "import { Block, Component, Context, createScopedContext, evalGet } from \"..\";\nimport { isArray, isObject } from \"../util\";\n\nconst forAliasRE = /([\\s\\S]*?)\\s+(?:in|of)\\s+([\\s\\S]*)/;\nconst forIteratorRE = /,([^,\\}\\]]*)(?:,([^,\\}\\]]*))?$/;\nconst stripParensRE = /^\\(|\\)$/g;\nconst destructureRE = /^[{[]\\s*((?:[\\w_$]+\\s*,?\\s*)+)[\\]}]$/;\n\ntype KeyToIndexMap = Map;\n\nexport const _for = (el: Element, exp: string, ctx: Context, component?: Component, componentProps?: Record, allProps?: Record) => {\n const inMatch = exp.match(forAliasRE);\n if (!inMatch) {\n console.warn(`invalid :for expression: ${exp}`);\n return;\n }\n\n const nextNode = el.nextSibling;\n\n const parent = el.parentElement!;\n const anchor = new Text(\"\");\n parent.insertBefore(anchor, el);\n parent.removeChild(el);\n\n const sourceExp = inMatch[2].trim();\n let valueExp = inMatch[1].trim().replace(stripParensRE, \"\").trim();\n let destructureBindings: string[] | undefined;\n let isArrayDestructure = false;\n let indexExp: string | undefined;\n let objIndexExp: string | undefined;\n\n let keyAttr = \"key\";\n let keyExp = el.getAttribute(keyAttr) || el.getAttribute((keyAttr = \":key\")) || el.getAttribute((keyAttr = \":key:bind\"));\n if (keyExp) {\n el.removeAttribute(keyAttr);\n if (keyAttr === \"key\") keyExp = JSON.stringify(keyExp);\n }\n\n let match: any;\n if ((match = valueExp.match(forIteratorRE))) {\n valueExp = valueExp.replace(forIteratorRE, \"\").trim();\n indexExp = match[1].trim();\n if (match[2]) {\n objIndexExp = match[2].trim();\n }\n }\n\n if ((match = valueExp.match(destructureRE))) {\n destructureBindings = match[1].split(\",\").map((s: string) => s.trim());\n isArrayDestructure = valueExp[0] === \"[\";\n }\n\n let mounted = false;\n let blocks: Block[];\n let childCtxs: Context[];\n let keyToIndexMap: Map;\n\n const createChildContexts = (source: unknown): [Context[], KeyToIndexMap] => {\n const map: KeyToIndexMap = new Map();\n const ctxs: Context[] = [];\n\n if (isArray(source)) {\n for (let i = 0; i < source.length; i++) {\n ctxs.push(createChildContext(map, source[i], i));\n }\n } else if (typeof source === \"number\") {\n for (let i = 0; i < source; i++) {\n ctxs.push(createChildContext(map, i + 1, i));\n }\n } else if (isObject(source)) {\n let i = 0;\n for (const key in source) {\n ctxs.push(createChildContext(map, source[key], i++, key));\n }\n }\n\n return [ctxs, map];\n };\n\n const createChildContext = (map: KeyToIndexMap, value: any, index: number, objKey?: string): Context => {\n const data: any = {};\n if (destructureBindings) {\n destructureBindings.forEach((b, i) => (data[b] = value[isArrayDestructure ? i : b]));\n } else {\n data[valueExp] = value;\n }\n if (objKey) {\n indexExp && (data[indexExp] = objKey);\n objIndexExp && (data[objIndexExp] = index);\n } else {\n indexExp && (data[indexExp] = index);\n }\n\n const childCtx = createScopedContext(ctx, data);\n const key = keyExp ? evalGet(childCtx.scope, keyExp, el) : index;\n map.set(key, index);\n childCtx.key = key;\n return childCtx;\n };\n\n const mountBlock = (ctx: Context, ref: Node) => {\n const block = new Block({ element: el, parentContext: ctx, replacementType: \"replace\", component, componentProps, allProps });\n block.key = ctx.key;\n block.insert(parent, ref);\n return block;\n };\n\n ctx.effect(() => {\n const source = evalGet(ctx.scope, sourceExp, el);\n const prevKeyToIndexMap = keyToIndexMap;\n [childCtxs, keyToIndexMap] = createChildContexts(source);\n if (!mounted) {\n blocks = childCtxs.map((s) => mountBlock(s, anchor));\n mounted = true;\n } else {\n for (let i = 0; i < blocks.length; i++) {\n if (!keyToIndexMap.has(blocks[i].key)) {\n blocks[i].remove();\n }\n }\n\n const nextBlocks: Block[] = [];\n let i = childCtxs.length;\n let nextBlock: Block | undefined;\n let prevMovedBlock: Block | undefined;\n while (i--) {\n const childCtx = childCtxs[i];\n const oldIndex = prevKeyToIndexMap.get(childCtx.key);\n let block: Block;\n if (oldIndex == null) {\n // new\n block = mountBlock(childCtx, nextBlock ? nextBlock.element : anchor);\n } else {\n // update\n block = blocks[oldIndex];\n Object.assign(block.context.scope, childCtx.scope);\n if (oldIndex !== i) {\n // moved\n if (\n blocks[oldIndex + 1] !== nextBlock ||\n // If the next has moved, it must move too\n prevMovedBlock === nextBlock\n ) {\n prevMovedBlock = block;\n block.insert(parent, nextBlock ? nextBlock.element : anchor);\n }\n }\n }\n nextBlocks.unshift((nextBlock = block));\n }\n blocks = nextBlocks;\n }\n });\n\n return nextNode;\n};\n", "import { Block, Component, Context, evalGet } from \"..\";\nimport { checkAndRemoveAttribute } from \"../util\";\n\ninterface Branch {\n exp?: string | null;\n el: Element;\n}\n\nexport function _if(el: Element, exp: string, ctx: Context, component?: Component, componentProps?: Record, allProps?: Record) {\n const parent = el.parentElement!;\n const anchor = new Comment(\":if\");\n\n parent.insertBefore(anchor, el);\n\n const branches: Branch[] = [{ exp, el }];\n\n let elseEl: Element | null;\n let elseExp: string | null;\n\n while ((elseEl = el.nextElementSibling)) {\n elseExp = null;\n\n if (checkAndRemoveAttribute(elseEl, \":else\") === \"\" || (elseExp = checkAndRemoveAttribute(elseEl, \":else-if\"))) {\n parent.removeChild(elseEl);\n branches.push({ exp: elseExp, el: elseEl });\n } else {\n break;\n }\n }\n\n const nextNode = el.nextSibling;\n parent.removeChild(el);\n\n let block: Block | undefined;\n let activeBranchIndex = -1;\n\n const removeActiveBlock = () => {\n if (block) {\n parent.insertBefore(anchor, block.element);\n block.remove();\n block = undefined;\n }\n };\n\n ctx.effect(() => {\n for (let i = 0; i < branches.length; i++) {\n const { exp, el } = branches[i];\n\n if (!exp || evalGet(ctx.scope, exp, el)) {\n if (i !== activeBranchIndex) {\n removeActiveBlock();\n block = new Block({ element: el, parentContext: ctx, replacementType: \"replace\", component, componentProps, allProps });\n block.insert(parent, anchor);\n parent.removeChild(anchor);\n activeBranchIndex = i;\n }\n\n return;\n }\n }\n\n activeBranchIndex = -1;\n removeActiveBlock();\n });\n\n return nextNode.parentElement;\n}\n", "import { Context, evalGet } from \"../\";\nimport { insertAfter, toDisplayString } from \"../util\";\n\ninterface InterpolationDirectiveOptions {\n element: Text;\n context: Context;\n}\n\nconst delims = /{{\\s?(.*?)\\s?}}/g;\n\nexport class InterpolationDirective {\n element: Text;\n context: Context;\n textNodes: Map = new Map();\n\n constructor({ element, context }: InterpolationDirectiveOptions) {\n this.element = element;\n this.context = context;\n\n this.findNodes();\n\n this.textNodes.forEach((nodes, expression) => {\n const trimmedExpression = expression.slice(2, -2).trim();\n\n nodes.forEach((node) => {\n const getter = (exp = trimmedExpression) => evalGet(this.context.scope, exp, element);\n\n context.effect(() => {\n node.textContent = toDisplayString(getter());\n });\n });\n });\n }\n\n findNodes() {\n const textContent = this.element.textContent.trim();\n if (textContent?.match(delims)) {\n const textNodes = textContent.split(/(\\{\\{\\s?[^}]+\\s?\\}\\})/g).filter(Boolean);\n if (textNodes) {\n let previousNode = this.element;\n\n for (let i = 0; i < textNodes.length; i++) {\n const textNode = textNodes[i];\n\n if (textNode.match(/\\{\\{\\s?.+\\s?\\}\\}/)) {\n const newNode = document.createTextNode(textNode);\n\n if (i === 0) {\n this.element.replaceWith(newNode);\n } else {\n insertAfter(newNode, previousNode);\n }\n\n previousNode = newNode;\n\n if (this.textNodes.has(textNode)) {\n this.textNodes.get(textNode).push(newNode);\n } else {\n this.textNodes.set(textNode, [newNode]);\n }\n } else {\n const newNode = document.createTextNode(textNodes[i]);\n\n if (i === 0) {\n this.element.replaceWith(newNode);\n } else {\n insertAfter(newNode, previousNode);\n }\n\n previousNode = newNode;\n }\n }\n }\n }\n }\n\n update() {}\n}\n", "import { Context, evalGet } from \"..\";\n\ninterface ShowDirectiveOptions {\n element: Element;\n context: Context;\n expression: string;\n}\n\nexport class ShowDirective {\n element: Element;\n context: Context;\n expression: string;\n originalDisplay: string;\n\n constructor({ element, context, expression }: ShowDirectiveOptions) {\n this.element = element;\n this.context = context;\n this.expression = expression;\n this.originalDisplay = getComputedStyle(this.element).display;\n\n context.effect(this.update.bind(this));\n }\n\n update() {\n const shouldShow = Boolean(evalGet(this.context.scope, this.expression, this.element));\n // @ts-ignore\n this.element.style.display = shouldShow ? this.originalDisplay : \"none\";\n }\n}\n", "import { Block, Component, Context } from \"..\";\nimport { insertBefore } from \"../util\";\n\nexport function _teleport(el: Element, exp: string, ctx: Context, component?: Component, componentProps?: Record, allProps?: Record) {\n const anchor = new Comment(\":teleport anchor\");\n insertBefore(anchor, el);\n const observed = new Comment(\":teleport\");\n el.replaceWith(observed);\n console.log(\"Creating new block with allProps\", component);\n\n const target = document.querySelector(exp);\n if (!target) {\n console.warn(`teleport target not found: ${exp}`);\n return;\n }\n\n // Prevent interpolations flashing before they can be compiled due to nextTick,\n // which is apparently required.\n // @ts-ignore\n const originalDisplay = el.style.display;\n // @ts-ignore\n el.style.display = \"none\";\n\n let block: Block;\n\n target.appendChild(el);\n\n const observer = new MutationObserver((mutationsList) => {\n mutationsList.forEach((mutation) => {\n mutation.removedNodes.forEach((removedNode) => {\n if (removedNode.contains(observed)) {\n if (block.element) block.remove();\n observer.disconnect();\n }\n });\n });\n });\n\n observer.observe(document.body, { childList: true, subtree: true });\n\n // @ts-ignore\n el.style.display = originalDisplay;\n\n block = new Block({\n element: el,\n parentContext: ctx,\n replacementType: \"replace\",\n component,\n componentProps,\n allProps,\n });\n\n // Return the anchor so walk continues down the tree in the right order.\n return anchor;\n}\n", "import { Context, evalGet, evalSet } from \"..\";\n\ninterface ValueDirectiveOptions {\n element: Element;\n context: Context;\n expression: string;\n}\n\ntype SupportedModelType = \"text\" | \"checkbox\" | \"radio\" | \"number\" | \"password\" | \"color\";\n\nfunction isInput(element: Element): element is HTMLInputElement {\n return element instanceof HTMLInputElement;\n}\n\nfunction isTextarea(element: Element): element is HTMLTextAreaElement {\n return element instanceof HTMLTextAreaElement;\n}\n\nfunction isSelect(element: Element): element is HTMLSelectElement {\n return element instanceof HTMLSelectElement;\n}\n\nexport class ValueDirective {\n element: Element;\n context: Context;\n expression: string;\n inputType: SupportedModelType;\n\n constructor({ element, context, expression }: ValueDirectiveOptions) {\n this.element = element;\n this.context = context;\n this.expression = expression;\n this.inputType = element.getAttribute(\"type\") as SupportedModelType;\n\n // Element -> Context\n if (isInput(element)) {\n switch (this.inputType) {\n case \"text\":\n case \"password\":\n case \"number\":\n case \"color\":\n element.addEventListener(\"input\", () => {\n const value = this.inputType === \"number\" ? (element.value ? parseFloat(element.value) : 0) : element.value;\n evalSet(this.context.scope, expression, value);\n });\n break;\n\n case \"checkbox\":\n element.addEventListener(\"change\", (e: any) => {\n evalSet(this.context.scope, expression, !!e.currentTarget.checked);\n });\n break;\n case \"radio\":\n element.addEventListener(\"change\", (e: any) => {\n if (e.currentTarget.checked) {\n evalSet(this.context.scope, expression, element.getAttribute(\"value\"));\n }\n });\n break;\n default:\n break;\n }\n }\n\n if (isTextarea(element)) {\n element.addEventListener(\"input\", () => {\n evalSet(this.context.scope, expression, element.value);\n });\n }\n\n if (isSelect(element)) {\n element.addEventListener(\"change\", () => {\n evalSet(this.context.scope, expression, element.value);\n });\n }\n\n // Context -> Element\n context.effect(this.updateElementValue.bind(this));\n }\n\n updateElementValue() {\n const value = evalGet(this.context.scope, this.expression, this.element);\n\n if (isInput(this.element)) {\n switch (this.inputType) {\n case \"text\":\n case \"password\":\n case \"number\":\n case \"color\":\n this.element.value = value;\n break;\n case \"checkbox\":\n this.element.checked = !!value;\n break;\n case \"radio\":\n this.element.checked = this.element.value === value;\n break;\n default:\n break;\n }\n }\n\n if (isTextarea(this.element)) {\n this.element.value = value;\n }\n\n if (isSelect(this.element)) {\n this.element.value = value;\n }\n }\n}\n", "interface EffectOptions {\n lazy?: boolean;\n}\n\ninterface EffectFunction {\n active: boolean;\n handler: () => void;\n refs: Set[];\n}\n\ntype Effects = Set;\ntype EffectsMap = Map;\ntype TargetMap = WeakMap;\n\nconst targetMap: TargetMap = new WeakMap();\nconst effectStack: (EffectFunction | undefined)[] = [];\n\nexport function track(target: T, key: PropertyKey) {\n const activeEffect = effectStack[effectStack.length - 1];\n\n if (!activeEffect) return;\n\n let effectsMap = targetMap.get(target);\n if (!effectsMap)\n targetMap.set(target, (effectsMap = new Map() as EffectsMap));\n\n let effects = effectsMap.get(key);\n if (!effects) effectsMap.set(key, (effects = new Set()));\n\n if (!effects.has(activeEffect)) {\n effects.add(activeEffect);\n activeEffect.refs.push(effects);\n }\n}\n\nexport function trigger(target: any, key: PropertyKey) {\n const effectsMap = targetMap.get(target);\n if (!effectsMap) return;\n\n const scheduled = new Set();\n\n effectsMap.get(key)?.forEach((effect) => {\n scheduled.add(effect);\n });\n\n scheduled.forEach(run);\n}\n\nfunction stop(effect: EffectFunction) {\n if (effect.active) cleanup(effect);\n effect.active = false;\n}\n\nfunction start(effect: EffectFunction) {\n if (!effect.active) {\n effect.active = true;\n run(effect);\n }\n}\n\nfunction run(effect: EffectFunction): unknown {\n if (!effect.active) return;\n\n if (effectStack.includes(effect)) return;\n\n cleanup(effect);\n\n let val: unknown;\n\n try {\n effectStack.push(effect);\n val = effect.handler();\n } finally {\n effectStack.pop();\n }\n\n return val;\n}\n\nfunction cleanup(effect: EffectFunction) {\n const { refs } = effect;\n\n if (refs.length) {\n for (const ref of refs) {\n ref.delete(effect);\n }\n }\n\n refs.length = 0;\n}\n\nexport function effect(handler: () => void, opts: EffectOptions = {}) {\n const { lazy } = opts;\n const newEffect: EffectFunction = {\n active: !lazy,\n handler,\n refs: [],\n };\n\n run(newEffect);\n\n return {\n start: () => {\n start(newEffect);\n },\n stop: () => {\n stop(newEffect);\n },\n toggle: () => {\n if (newEffect.active) {\n stop(newEffect);\n } else {\n start(newEffect);\n }\n return newEffect.active;\n },\n };\n}\n", "import { isObject } from \"../util\";\nimport { effect } from \"./effect\";\n\nconst $computed = Symbol(\"computed\");\n\nexport type Computed = {\n readonly value: T;\n readonly [$computed]: true;\n};\n\nexport function isComputed(value: unknown): value is Computed {\n return isObject(value) && value[$computed];\n}\n\nexport function computed(getter: () => T): Computed {\n const ref = {\n get value(): T {\n return getter();\n },\n [$computed]: true,\n } as const;\n\n effect(() => {\n getter();\n });\n\n return ref;\n}\n", "import { isObject } from \"../util\";\nimport { track, trigger } from \"./effect\";\nimport { Reactive, reactive } from \"./reactive\";\n\nexport const $ref = Symbol(\"ref\");\n\nexport type Ref = {\n value: T;\n [$ref]: true;\n};\n\nexport function isRef(value: unknown): value is Ref {\n return isObject(value) && !!value[$ref];\n}\n\nexport function ref(value: T = null as unknown as T): Ref {\n if (isObject(value)) {\n // @ts-ignore\n return isRef(value) ? (value as Ref) : (reactive(value) as Reactive);\n }\n\n const result = { value, [$ref]: true };\n\n return new Proxy(result, {\n get(target: object, key: string | symbol, receiver: any) {\n const val = Reflect.get(target, key, receiver);\n track(result, \"value\");\n return val;\n },\n set(target: object, key: string | symbol, value: unknown) {\n const oldValue = target[key];\n if (oldValue !== value) {\n const success = Reflect.set(target, key, value);\n if (success) {\n trigger(result, \"value\");\n }\n }\n return true;\n },\n }) as Ref;\n}\n", "import { isObject } from \"../util\";\nimport { track, trigger } from \"./effect\";\nimport { ref } from \"./ref\";\n\nconst $reactive = Symbol(\"reactive\");\n\nexport type Reactive = T & { [$reactive]: true };\n\nexport function isReactive(\n value: unknown,\n): value is Reactive {\n return isObject(value) && !!value[$reactive];\n}\n\nexport function reactive(value: T): Reactive {\n // @ts-ignore\n if (!isObject(value)) return ref(value) as Reactive;\n if (value[$reactive]) return value as Reactive;\n\n value[$reactive] = true;\n\n Object.keys(value).forEach((key) => {\n if (isObject(value[key])) {\n value[key] = reactive(value[key]);\n }\n });\n\n return new Proxy(value, reactiveProxyHandler()) as Reactive;\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", "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 {\n checkAndRemoveAttribute,\n componentHasPropByName,\n extractPropName,\n findSlotNodes,\n findTemplateNodes,\n isElement,\n isEventAttribute,\n isMirrorProp,\n isObject,\n isPropAttribute,\n isRegularProp,\n isSpreadProp,\n isText,\n Slot,\n stringToElement,\n Template,\n toDisplayString,\n} 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._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 app: this,\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 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\n let proxy: any;\n\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\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 app?: App;\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();\n parentComponentBlock: Block | undefined;\n componentProps: Record;\n allProps: Record;\n\n isFragment: boolean;\n start?: Text;\n end?: Text;\n key?: any;\n\n constructor(opts: BlockOptions) {\n this.isFragment = opts.element instanceof HTMLTemplateElement;\n this.parentComponentBlock = opts.parentComponentBlock;\n\n if (opts.component) {\n current.componentBlock = this;\n this.element = stringToElement(opts.component.template);\n } else {\n if (this.isFragment) {\n this.element = (opts.element as HTMLTemplateElement).content.cloneNode(true) as Element;\n } else if (typeof opts.element === \"string\") {\n this.element = stringToElement(opts.element);\n } else {\n this.element = opts.element.cloneNode(true) as Element;\n opts.element.replaceWith(this.element);\n }\n }\n\n if (opts.isRoot) {\n this.context = opts.parentContext;\n opts.app.root = this;\n } else {\n this.parentContext = opts.parentContext ? opts.parentContext : createContext({});\n this.parentContext.blocks.push(this);\n this.context = createContext({ parentContext: opts.parentContext, app: opts.app });\n }\n\n if (opts.component) {\n this.componentProps = mergeProps(opts.componentProps ?? {}, opts.component.props ?? {});\n\n if (opts.component.main) {\n this.context.scope = {\n ...(opts.component.main(this.componentProps) || {}),\n };\n }\n }\n\n opts.allProps?.forEach((prop: any) => {\n if (prop.isBind) {\n this.context.effect(() => {\n let newValue: unknown;\n\n if (prop.isSpread) {\n const spreadProps = evalGet(this.parentContext.scope, prop.extractedName);\n if (isObject(spreadProps)) {\n Object.keys(spreadProps).forEach((key) => {\n newValue = spreadProps[key];\n this.setProp(key, newValue);\n });\n }\n } else {\n newValue = prop.isMirror ? evalGet(this.parentContext.scope, prop.extractedName) : evalGet(this.parentContext.scope, prop.exp);\n this.setProp(prop.extractedName, newValue);\n }\n });\n }\n });\n\n // Capture slots\n this.context.slots = findSlotNodes(this.element);\n this.context.templates = opts.templates ?? [];\n\n // Put templates into slots\n this.context.slots.forEach((slot) => {\n const template = this.context.templates.find((t) => t.targetSlotName === slot.name);\n\n if (template) {\n const templateContents = template.node.content.cloneNode(true);\n slot.node.replaceWith(templateContents);\n }\n });\n\n this.context.scope.$isRef = isRef;\n this.context.scope.$isComputed = isComputed;\n\n walk(this.element, this.context);\n\n if (opts.component) {\n if (opts.replacementType === \"replace\") {\n if (opts.element instanceof HTMLElement) {\n opts.element.replaceWith(this.element);\n }\n } else {\n if (opts.element instanceof HTMLElement) {\n opts.element.replaceChildren(this.element);\n }\n }\n }\n }\n\n setProp(name: string, value: unknown) {\n if (isRef(this.componentProps[name])) {\n this.componentProps[name].value = value;\n } else {\n this.componentProps[name] = value;\n }\n }\n\n insert(parent: Element, anchor: Node | null = null) {\n if (this.isFragment) {\n if (this.start) {\n // Already inserted, moving\n let node: Node | null = this.start;\n let next: Node | null;\n\n while (node) {\n next = node.nextSibling;\n parent.insertBefore(node, anchor);\n\n if (node === this.end) {\n break;\n }\n\n node = next;\n }\n } else {\n this.start = new Text(\"\");\n this.end = new Text(\"\");\n\n parent.insertBefore(this.end, anchor);\n parent.insertBefore(this.start, this.end);\n parent.insertBefore(this.element, this.end);\n }\n } else {\n parent.insertBefore(this.element, anchor);\n }\n }\n\n remove() {\n if (this.parentContext) {\n const i = this.parentContext.blocks.indexOf(this);\n\n if (i > -1) {\n this.parentContext.blocks.splice(i, 1);\n }\n }\n\n if (this.start) {\n const parent = this.start.parentNode!;\n let node: Node | null = this.start;\n let next: Node | null;\n\n while (node) {\n next = node.nextSibling;\n parent.removeChild(node);\n\n if (node === this.end) {\n break;\n }\n\n node = next;\n }\n } else {\n this.element.remove();\n }\n\n this.teardown();\n }\n\n teardown() {\n this.context.blocks.forEach((block) => {\n block.teardown();\n });\n\n this.context.effects.forEach((e) => e.stop());\n }\n}\n\nfunction isComponent(element: Element, context: Context) {\n return !!context.app.getComponent(element.tagName.toLowerCase());\n}\n\nfunction warnInvalidDirectives(node: Element, directives: string[]): boolean {\n if (directives.every((d) => node.hasAttribute(d))) {\n console.warn(`These directives cannot be used together on the same node:`, directives);\n console.warn(\"Node ignored:\", node);\n return true;\n }\n\n return false;\n}\n\nfunction walk(node: Node, context: Context) {\n if (isText(node)) {\n new InterpolationDirective({ element: node, context });\n return;\n }\n\n if (isElement(node)) {\n let exp: string | null;\n\n const handleDirectives = (node: Element, context: Context, component?: Component, componentProps?: Record, allProps?: any[]) => {\n if (warnInvalidDirectives(node, [\":if\", \":for\"])) return;\n if (warnInvalidDirectives(node, [\":for\", \":teleport\"])) return;\n if (warnInvalidDirectives(node, [\":if\", \":teleport\"])) return;\n\n // e.g.
\n // In this case, the scope is merged into context.scope and will overwrite\n // anything returned from `main`.\n if ((exp = checkAndRemoveAttribute(node, \":scope\"))) {\n const scope = evalGet(context.scope, exp, node);\n if (typeof scope === \"object\") {\n Object.assign(context.scope, scope);\n // context = createScopedContext(context, scope);\n }\n }\n\n if ((exp = checkAndRemoveAttribute(node, \":if\"))) {\n return _if(node, exp, context, component, componentProps, allProps);\n }\n if ((exp = checkAndRemoveAttribute(node, \":for\"))) {\n return _for(node, exp, context, component, componentProps, allProps);\n }\n if ((exp = checkAndRemoveAttribute(node, \":teleport\"))) {\n return _teleport(node, exp, context, component, componentProps, allProps);\n }\n if ((exp = checkAndRemoveAttribute(node, \":show\"))) {\n new ShowDirective({ element: node, context, expression: exp });\n }\n if ((exp = checkAndRemoveAttribute(node, \":ref\"))) {\n context.scope[exp].value = node;\n }\n if ((exp = checkAndRemoveAttribute(node, \":value\"))) {\n new ValueDirective({ element: node, context, expression: exp });\n }\n if ((exp = checkAndRemoveAttribute(node, \":html\"))) {\n const htmlExp = exp;\n\n context.effect(() => {\n const result = evalGet(context.scope, htmlExp, node);\n if (result instanceof Element) {\n node.replaceChildren();\n node.append(result);\n } else {\n node.innerHTML = result;\n }\n });\n }\n if ((exp = checkAndRemoveAttribute(node, \":text\"))) {\n const textExp = exp;\n\n context.effect(() => {\n node.textContent = toDisplayString(evalGet(context.scope, textExp, node));\n });\n }\n };\n\n const processAttributes = (node: Element, component?: Component) => {\n return Array.from(node.attributes)\n .filter((attr) => isSpreadProp(attr.name) || isMirrorProp(attr.name) || (isRegularProp(attr.name) && componentHasPropByName(extractPropName(attr.name), component)))\n .map((attr) => ({\n isMirror: isMirrorProp(attr.name),\n isSpread: isSpreadProp(attr.name),\n isBind: attr.name.includes(\"bind\"),\n originalName: attr.name,\n extractedName: extractPropName(attr.name),\n exp: attr.value,\n value: isMirrorProp(attr.name) ? evalGet(context.scope, extractPropName(attr.name), node) : attr.value ? evalGet(context.scope, attr.value, node) : undefined,\n }));\n };\n\n if (isComponent(node, context)) {\n const component = context.app.getComponent(node.tagName.toLowerCase());\n const allProps = processAttributes(node, component);\n\n const componentProps = allProps.reduce((acc, { isSpread, isMirror, extractedName, value }) => {\n if (isSpread) {\n const spread = evalGet(context.scope, extractedName, node);\n if (isObject(spread)) Object.assign(acc, spread);\n } else if (isMirror) {\n acc[extractedName] = evalGet(context.scope, extractedName, node);\n } else {\n acc[extractedName] = value;\n }\n return acc;\n }, {});\n\n const next = handleDirectives(node, context, component, componentProps, allProps);\n if (next) return next;\n\n const templates = findTemplateNodes(node);\n\n return new Block({\n element: node,\n app: current.componentBlock.context.app,\n // parentContext: context,\n component,\n replacementType: \"replace\",\n parentComponentBlock: current.componentBlock,\n templates,\n componentProps,\n allProps,\n }).element;\n }\n\n const next = handleDirectives(node, context);\n if (next) return next;\n\n Array.from(node.attributes).forEach((attr) => {\n if (isPropAttribute(attr.name)) {\n new AttributeDirective({ element: node, context, attr });\n }\n\n if (isEventAttribute(attr.name)) {\n new EventDirective({ element: node, context, attr });\n }\n });\n\n walkChildren(node, context);\n }\n}\n\nfunction walkChildren(node: Node, context: Context) {\n let child = node.firstChild;\n\n while (child) {\n child = walk(child, context) || child.nextSibling;\n }\n}\n\nconst evalFuncCache: Record = {};\n\nexport function evalGet(scope: any, exp: string, el?: Node) {\n if (!exp.trim()) return undefined;\n return execute(scope, `const ___value = (${exp.trim()}); return ___value;`, el);\n}\n\nexport function evalSet(scope: any, exp: string, value: unknown) {\n value = typeof value === \"string\" ? `\"${value}\"` : value;\n return execute(scope, `const ___target = (${exp.trim()}); return $isRef(___target) ? ___target.value = ${value} : ___target = ${value};`, null, false);\n}\n\nfunction execute(scope: any, exp: string, el?: Node, flatRefs = true) {\n const newScope = flatRefs ? flattenRefs(scope) : scope;\n const fn = evalFuncCache[exp] || (evalFuncCache[exp] = toFunction(exp));\n\n try {\n return fn(newScope, el);\n } catch (e) {\n console.warn(`Error evaluating expression: \"${exp}\":`);\n console.error(e);\n }\n}\n\n// Function to convert expression strings to functions\nfunction toFunction(exp: string) {\n try {\n return new Function(\"$data\", \"$el\", `with($data){${exp}}`);\n } catch (e) {\n console.error(`${(e as Error).message} in expression: ${exp}`);\n return () => {};\n }\n}\n\n// Map all ref properties in scope to their `.value`\nfunction flattenRefs(scope: any): any {\n const mapped = {};\n\n for (const key in scope) {\n if (scope.hasOwnProperty(key)) {\n // Check if the value is a Ref\n if (isRef(scope[key])) {\n mapped[key] = scope[key].value;\n } else {\n mapped[key] = scope[key];\n }\n }\n }\n return mapped;\n}\n", "import { App } from \".\";\nimport { computed } from \"./reactivity/computed\";\nimport { reactive } from \"./reactivity/reactive\";\nimport { html } from \"./util\";\n\n// ------------------------------------------------\n// Slots, multiple default and named, :if and :for\n// const card = {\n// template: html`
\n//

\n// \n//
`,\n// };\n\n// const main = {\n// template: html`\n//
\n//

card below

\n// \n// card title\n// \n// \n//
\n// `,\n// };\n\n// const app = new App();\n// app.register(\"card\", card);\n// app.mount(main, \"#app\");\n\n// ------------------------------------------------\n// Slots, multiple default and named, :if and :for\n// const app = new App();\n\n// const parent = {\n// template: html`\n//
\n//

parent

\n// \n// \n//
\n//
\n//
1
\n//
2
\n//
3
\n//
\n//
\n// content 1 always shown\n//
\n// content 2, animals:\n//
animal: {{animal}}
\n//
\n\n// \n// \n//
\n//
\n// `,\n// main() {\n// const bool = ref(true);\n// const animals = reactive([\"dog\", \"cat\", \"bear\"]);\n\n// setInterval(() => {\n// bool.value = !bool.value;\n// }, 2000);\n\n// return { bool, animals };\n// },\n// };\n// const card = {\n// template: html`
\n//

card

\n//

\n// \n//
`,\n// };\n// app.register(\"card\", card);\n// const parentBlock = app.mount(parent, \"body\");\n// const cardBlock = parentBlock.context.blocks[0];\n\n// ------------------------------------------------\n// Component pros, mirror and spread, bind and no bind\n// const child = {\n// template: html`
Animal: {{animal}} {{index}}
`,\n// props: { animal: { default: \"cat\" }, index: { default: 0 } },\n// main({ animal, index }) {\n// return { animal, index };\n// },\n// };\n\n// const parent = {\n// template: html`\n//
\n// \n// mirror, no bind:\n// \n//
\n// mirror, bind:\n// \n//
\n// spread, no bind:\n// \n//
\n// spread, bind:\n// \n//
\n// regular prop:\n// \n//
\n// regular prop, bind:\n// \n//
\n//
div has \"id\" set to animal.value
\n//
\n//
div has \"id\" set and bound to animal.value
\n//
\n//
div has \"animal\" set to animal.value
\n//
\n//
div has \"animal\" set and bound to animal.value
\n//
\n//
div has \"animal\" spread
\n//
\n//
div has \"animal\" spread and bound
\n//
\n//
\n//
\n//
\n// if bool, mirror, no bind:\n// \n// if bool, mirror, bind:\n// \n//
\n// for list, mirror, no bind:\n// \n//
\n// for list, mirror, bind:\n// \n// if bool, for list, mirror, no bind: these have the value \"DOG!\" because by the time for :for directive is evaluated, animal.value is \"DOG!\", and no longer \"dog\".\n//
\n// \n//
\n//
\n// `,\n// main() {\n// const bool = ref(false);\n// const animal = ref(\"dog\");\n// const spread = reactive({ animal: \"panther\" });\n// const list = reactive([1, 2, 3]);\n\n// setTimeout(() => {\n// spread.animal = \"PANTHER!\";\n// animal.value = \"DOG!\";\n// bool.value = true;\n// }, 500);\n\n// setTimeout(() => {\n// animal.value = \"DOG!!!!!\";\n// }, 1000);\n\n// return { animal, spread, bool, list };\n// },\n// };\n\n// const app = new App();\n// app.register(\"child\", child);\n// app.mount(parent, \"#app\");\n\n// ------------------------------------------------\n// Event directive\n// const counter = {\n// template: html`\n//
\n//
\n//
color is gray
\n//
color is white
\n//
{{style.color}}
\n//
\n//

Count: {{count}}{{count >= 2 ? '!!!' : ''}}

\n// \n// \n//
\n//
{{color}}
\n//
\n//
\n// `,\n// main() {\n// const count = ref(0);\n// const style = reactive({ color: \"gray\" });\n// const increment = () => count.value++;\n// const decrement = () => count.value--;\n//\n// setInterval(() => {\n// style.color = style.color === \"gray\" ? \"white\" : \"gray\";\n// }, 500);\n//\n// return { count, increment, decrement, style };\n// },\n// };\n//\n// const app = new App();\n// app.mount(counter, \"#app\");\n\n// ------------------------------------------------\n// :if above :for\n// :if with :teleport\n// const main = {\n// template: html`\n//
\n//
\n//
\n//
{{item}}
\n//
\n//
\n//
\n//
if bool teleported! {{items}}
\n//
\n//
\n// `,\n// main() {\n// const items = reactive([1, 2, 3, 4, 5]);\n// const bool = ref(true);\n// setInterval(() => (bool.value = !bool.value), 250);\n// return { items, bool };\n// },\n// };\n//\n// const app = new App();\n// app.mount(main, \"#app\");\n\n// ------------------------------------------------\n// :html\n// const main = {\n// template: html`
`,\n// main() {\n// const html = ref(\"

hello

\");\n\n// setTimeout(() => {\n// if (html.value === \"

hello

\") {\n// html.value = \"

world

\";\n// }\n// }, 1000);\n\n// return { html };\n// },\n// };\n\n// const app = new App();\n// app.mount(main, \"#app\");\n\n// ------------------------------------------------\n// Colors from css framework\nconst main = {\n template: html`\n
\n

phase

\n

Colors

\n \n
\n
\n
{{variant}}-{{rank}}
\n
\n
\n
\n
\n `,\n main() {\n const ranks = reactive([\"5\", \"10\", \"20\", \"30\", \"40\", \"50\", \"60\", \"70\", \"80\", \"90\"]);\n const basesReverse = computed(() => Array.from(ranks).reverse());\n const bg = (variant: string, rank: string, index: number) => ({ backgroundColor: `var(--${variant}-${rank})`, color: `var(--${variant}-${basesReverse.value[index]})` });\n\n return { ranks, bg };\n },\n};\n\nconst app = new App();\napp.mount(main, \"#app\");\n\n// ------------------------------------------------\n// :scope\n// const child = {\n// template: html`\n//
\n// I am child and I have food: \"{{food}}\" (does not inherit)\n//
\n// \n//
\n//
\n// `,\n// main() {\n// const food = ref(\"\uD83C\uDF54\");\n// return { food };\n// },\n// };\n\n// const main = {\n// template: html`\n//
\n//
\n//
Scoped food: {{food}} and scoped drink: {{drink}}
\n// Child slot, food: {{food}} {{drink}}\n//
\n//
\n// `,\n// main() {\n// return { food: ref(\"nothing\") };\n// },\n// };\n\n// const app = new App();\n// app.register(\"child\", child);\n// app.mount(main, \"#app\");\n\n// ------------------------------------------------\n// Practical :scope demo\n// const main = {\n// template: html`\n//
\n//
ON
\n//
OFF
\n// \n//
\n// `,\n// main() {\n// const onClick = () => {\n// console.log(\"ok\");\n// };\n//\n// return { onClick };\n// },\n// };\n//\n// const app = new App();\n// app.mount(main, \"#app\");\n\n// --------\n// weird issue\n// const child = {\n// template: html`
child{{thing}}
`,\n// props: { thing: { default: 1 } },\n// main({ thing }) {\n// return { thing };\n// },\n// };\n//\n// const counter = {\n// template: html`\n//
\n//
\n//
\n//
{{color}}
\n//
\n// \n//
\n//
\n// `,\n// main() {\n// const colors = reactive([\"red\", \"orange\"]);\n// return { colors };\n// },\n// };\n//\n// const app = new App();\n// app.register(\"child\", child);\n// app.mount(counter, \"#app\");\n\n// ------------------------------------------------\n//