micra.js 1.1.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -30
- package/dist/dom/query.d.ts +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/micra.cjs.js +32 -9
- package/dist/micra.cjs.js.map +2 -2
- package/dist/micra.esm.js +32 -9
- package/dist/micra.esm.js.map +2 -2
- package/dist/micra.js +32 -9
- package/dist/micra.js.map +2 -2
- package/dist/micra.min.js +2 -2
- package/dist/types.d.ts +9 -1
- package/package.json +1 -1
- package/src/dom/directives.ts +40 -5
- package/src/dom/each.ts +4 -1
- package/src/dom/events.ts +2 -2
- package/src/dom/query.ts +15 -1
- package/src/index.ts +1 -1
- package/src/types.ts +10 -1
- package/src/utils/fetch.ts +5 -3
package/dist/micra.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts", "../src/utils/fetch.ts", "../src/core/registry.ts", "../src/utils/expr.ts", "../src/core/bus.ts", "../src/core/reactive.ts", "../src/dom/query.ts", "../src/dom/directives.ts", "../src/dom/events.ts", "../src/dom/each.ts", "../src/dom/refs.ts", "../src/core/mount.ts", "../src/core/start.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Micra.js \u2014 Lightweight reactive framework for small sites and simple SaaS.\n *\n * Public surface \u2014 re-exports only.\n *\n * Features:\n * - JS expressions in directives (data-if=\"count > 0\")\n * - Keyed list diffing (data-each=\"items\" data-key=\"id\")\n * - Auto-mount via data-component (Micra.define + Micra.start)\n * - Props from SSR data-attributes (this.prop('page'))\n * - Built-in fetch helper (this.fetch('/api/...'))\n * - Global event bus (Micra.on / Micra.emit)\n * - DOM refs (data-ref=\"chart\" \u2192 this.refs.chart)\n * - Additive class toggling (data-class=\"active:isActive\")\n * - @event shorthand (@click=\"increment\")\n * - Lifecycle: onCreate, onDestroy\n * - SSR-friendly: Micra.start() is safe to call multiple times\n * - Directive cache: O(1) re-renders after first mount\n *\n * Size target: < 5 KB minified+gzipped\n *\n * @module Micra\n */\n\n// \u2500\u2500 Public types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport type {\n StateRecord,\n UnsubFn,\n EventHandler,\n FetchOptions,\n ComponentInstance,\n ComponentDefinition,\n} from './types'\n\n// \u2500\u2500 Errors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport { FetchError } from './utils/fetch'\n\n// \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport { define, defineComponent, instances, registry, debug } from './core/registry'\nexport { mount } from './core/mount'\nexport { start } from './core/start'\nexport { on, off, emit } from './core/bus'\n", "/**\n * src/utils/fetch.ts \u2014 HTTP fetch helper.\n *\n * Responsibilities:\n * - Auto-attach CSRF token from <meta name=\"csrf-token\">\n * - Serialize POST/PUT/PATCH body as JSON\n * - Serialize GET/HEAD options as query params\n * - Throw a typed FetchError on non-2xx responses\n * - Return parsed JSON or text\n *\n * LLM NOTE: This module is PURE (no DOM side effects beyond reading a meta tag).\n * It wraps the native fetch() API with SaaS-friendly defaults.\n */\n\nimport type { FetchOptions } from '../types'\n\n// \u2500\u2500 CSRF \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Read CSRF token from <meta name=\"csrf-token\"> (Rails, Laravel, Django\u2026). */\nfunction getCSRF(): string | null {\n return document.querySelector('meta[name=\"csrf-token\"]')?.getAttribute('content') ?? null\n}\n\n// \u2500\u2500 Typed error \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Thrown by `this.fetch()` when the server returns a non-2xx status.\n *\n * @example\n * try {\n * await this.fetch('/api/data')\n * } catch (e) {\n * if (e instanceof FetchError && e.status === 404) { ... }\n * }\n */\nexport class FetchError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly response: Response,\n ) {\n super(message)\n this.name = 'FetchError'\n }\n}\n\n// \u2500\u2500 Fetch helper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Fetch wrapper with SaaS defaults.\n *\n * - GET/HEAD: extra `options` keys become URL query params\n * - POST/PUT/PATCH/DELETE: `options.body` is JSON-serialized\n * - Attaches X-CSRF-Token header automatically\n * - Returns parsed JSON if Content-Type is application/json, else text\n *\n * @example\n * // GET with params \u2192 /api/users?page=2&status=active\n * const data = await this.fetch('/api/users', { page: 2, status: 'active' })\n *\n * // POST with JSON body\n * await this.fetch('/api/invite', { method: 'POST', body: { email, role } })\n */\nexport async function micraFetch(url: string, options: FetchOptions = {}): Promise<unknown> {\n const method = ((options.method as string | undefined) ?? 'GET').toUpperCase()\n const headers: Record<string, string> = {\n Accept: 'application/json',\n ...(options.headers as Record<string, string> | undefined),\n }\n\n const csrf = getCSRF()\n if (csrf) headers['X-CSRF-Token'] = csrf\n\n let finalUrl = url\n let body: string | undefined\n\n if (method === 'GET' || method === 'HEAD') {\n const params: Record<string, string> = {}\n for (const [k, v] of Object.entries(options)) {\n if (k !== 'method' && k !== 'headers' && v != null) params[k] = String(v)\n }\n if (Object.keys(params).length)\n finalUrl += (url.includes('?') ? '&' : '?') + new URLSearchParams(params)\n } else {\n headers['Content-Type'] = 'application/json'\n body = JSON.stringify(options.body !== undefined ? options.body : options)\n }\n\n const res = await fetch(finalUrl, {\n method,\n headers,\n ...(body !== undefined ? { body } : {}),\n })\n\n if (!res.ok)\n throw new FetchError(`[Micra] fetch: ${method} ${url} \u2192 ${res.status}`, res.status, res)\n\n const ct = res.headers.get('content-type') ?? ''\n return ct.includes('application/json') ? res.json() : res.text()\n}\n", "/**\n * src/core/registry.ts \u2014 Component definition registry and instance store.\n *\n * Responsibilities:\n * - Store named component definitions (define / registry)\n * - Store live component instances keyed by root HTMLElement (instances)\n *\n * LLM NOTE: Both maps are module-level singletons (one per page load).\n * They are intentionally mutable from mount.ts and start.ts.\n */\n\nimport type {\n ComponentDefinition,\n ComponentInstance,\n InternalInstance,\n StateRecord,\n} from '../types'\n\n// Named definition map \u2014 populated by define()\nexport const _registry = new Map<string, ComponentDefinition>()\n\n// Live instance map \u2014 populated by mount(), cleared by instance.destroy()\nexport const _instances = new Map<HTMLElement, InternalInstance>()\n\n// \u2500\u2500 Public accessors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Register a component definition under `name`.\n *\n * @example\n * define('counter', { state: { count: 0 }, inc() { this.state.count++ } })\n */\nexport function define<S extends StateRecord>(\n name: string,\n definition: ComponentDefinition<S>,\n): void {\n _registry.set(name, definition as ComponentDefinition)\n}\n\n/**\n * Type-helper \u2014 returns `definition` unchanged but lets TypeScript infer `S`\n * from the `state` literal so all methods are typed with the correct `this`.\n *\n * Use this when defining a component outside a `define()` call.\n *\n * @example\n * const counter = defineComponent({\n * state: { count: 0 },\n * increment() { this.state.count++ }, // this.state: { count: number } \u2713\n * })\n * Micra.define('counter', counter)\n */\nexport function defineComponent<S extends StateRecord>(\n definition: ComponentDefinition<S>,\n): ComponentDefinition<S> {\n return definition\n}\n\n/**\n * Returns a read-only view of all live instances (keyed by root element).\n * Useful for DevTools / debugging.\n */\nexport function instances(): ReadonlyMap<HTMLElement, ComponentInstance> {\n return _instances\n}\n\n/**\n * Returns a read-only view of all registered component definitions.\n * Useful for DevTools / debugging.\n */\nexport function registry(): ReadonlyMap<string, ComponentDefinition> {\n return _registry\n}\n\n/**\n * Print all live component instances to the browser console.\n * Shows component name, root element, and current state for each instance.\n *\n * @example\n * // In browser DevTools console:\n * Micra.debug()\n * // [Micra] 3 live component(s)\n * // counter $el: <div> state: { count: 5 }\n * // user-list $el: <div> state: { users: [...], loading: false }\n */\nexport function debug(): void {\n if (_instances.size === 0) {\n console.log('[Micra] No live components.')\n return\n }\n console.group(`[Micra] ${_instances.size} live component(s)`)\n for (const [el, instance] of _instances) {\n const name = el.getAttribute('data-component') ?? '(unnamed)'\n console.group(`%c${name}`, 'font-weight:bold;color:#6366f1')\n console.log('$el ', el)\n console.log('state', { ...instance.state })\n console.groupEnd()\n }\n console.groupEnd()\n}\n", "/**\n * src/utils/expr.ts \u2014 JS expression evaluator.\n *\n * Responsibilities:\n * - Compile expression strings into cached functions\n * - Evaluate them against a state object\n * - Fast-path for simple property lookups\n * - Shadow non-state identifiers so directive expressions cannot reach\n * globals like `window`, `fetch`, `constructor`, etc. A small whitelist\n * of utility globals (Math, JSON, Date, ...) remains accessible.\n *\n * LLM NOTE: This module is PURE. It does not touch the DOM or mutate state.\n *\n * Security model:\n * Directive expressions are JavaScript \u2014 they are compiled via `new Function`\n * and run with full JS capability except that bare identifiers must resolve\n * to either a state key, a component instance method, or one of\n * ALLOWED_GLOBALS. This blocks the `constructor.constructor(\"...\")()` chain\n * and accidental access to `window` / `document` / `fetch`. It does NOT\n * sandbox method calls \u2014 if a component method itself touches `window`,\n * that still works. Treat directive templates as trusted code regardless.\n */\n\nimport type { StateRecord } from '../types'\n\n// \u2500\u2500 Expression cache \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Compiled functions are keyed by expression string \u2014 Function() is only called\n// once per unique expression across the entire app lifetime.\n\n// LLM NOTE: exprCache is module-level (shared across all components).\n// This is intentional \u2014 most apps reuse the same expressions.\ntype Compiled = (state: object, safe: object) => unknown\nconst exprCache = new Map<string, Compiled>()\n// Expressions whose runtime error we have already warned about. Prevents log spam\n// when the same `data-text=\"item.naame\"` typo fires every render.\nconst warnedRuntime = new Set<string>()\n\n// Simple identifier or dot-path: \"count\", \"user.name\", \"item.email\"\n// Matches: letter/$/_ followed by word chars, optionally with .property chains\nconst SIMPLE_PATH = /^[a-zA-Z_$][a-zA-Z0-9_$]*(\\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/\n\n// \u2500\u2500 Safe scope \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Globals reachable from directive expressions. Anything else (window, fetch,\n * constructor, eval, ...) is shadowed by SAFE_OUTER and resolves to undefined.\n */\nconst ALLOWED_GLOBALS = new Set<string>([\n 'Math', 'JSON', 'Date', 'String', 'Number', 'Boolean', 'Array', 'Object',\n 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'NaN', 'Infinity', 'undefined',\n])\n\n/**\n * Outer `with()` scope. Its `has` trap claims every non-whitelisted identifier\n * is \"in scope\" so the JS engine resolves the read on this Proxy (which returns\n * undefined) instead of walking up to the global object. Whitelisted names fall\n * through to globalThis.\n */\n// Sentinel parameter names used by the compiled function. SAFE_OUTER must NOT\n// shadow them, or `with($s)` would resolve to `undefined` via SAFE_OUTER.\nconst PARAM_S = '$s'\nconst PARAM_SAFE = '$safe'\n\nconst SAFE_OUTER: object = new Proxy(Object.create(null) as object, {\n has(_target, key): boolean {\n if (typeof key !== 'string') return false\n if (key === PARAM_S || key === PARAM_SAFE) return false\n return !ALLOWED_GLOBALS.has(key)\n },\n get(): undefined {\n return undefined\n },\n})\n\n/**\n * @internal Per-state safe wrappers \u2014 one per source state object. WeakMap so\n * short-lived itemStates get GC'd with their wrappers.\n */\nconst safeWrapCache = new WeakMap<object, object>()\n\n/**\n * @internal Pre-computed names that live on `Object.prototype`\n * (constructor, toString, hasOwnProperty, ...). Used by safeStateHas to detect\n * built-in keys without re-walking the chain on every call.\n */\nconst OBJ_PROTO_KEYS = new Set<string>(Object.getOwnPropertyNames(Object.prototype))\n\n/**\n * Wrap a state object so its `has` trap reports only \"real\" keys \u2014 own\n * properties or keys reachable up to (but not including) `Object.prototype`.\n * This blocks `'constructor' in state` from leaking the prototype.\n */\nfunction safeStateWrap(state: object): object {\n const cached = safeWrapCache.get(state)\n if (cached) return cached\n const wrapped = new Proxy(state, {\n has(target, key) {\n return safeStateHas(target, key)\n },\n get(target, key) {\n return Reflect.get(target, key)\n },\n })\n safeWrapCache.set(state, wrapped)\n return wrapped\n}\n\n/**\n * Return true iff `key` is reachable on `state` without walking into\n * `Object.prototype`. Works for plain objects, prototype-chained objects, and\n * Proxies with their own `has` trap.\n */\nfunction safeStateHas(state: object, key: PropertyKey): boolean {\n if (typeof key !== 'string') return false\n if (!Reflect.has(state, key)) return false\n // Identifiers that are NOT on Object.prototype are always safe \u2014 accept them\n // immediately without walking the chain.\n if (!OBJ_PROTO_KEYS.has(key)) return true\n // Built-in Object.prototype names (constructor, toString, hasOwnProperty, ...)\n // are only accepted when they have been explicitly placed on the state chain.\n let obj: object | null = state\n while (obj && obj !== Object.prototype) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) return true\n obj = Object.getPrototypeOf(obj) as object | null\n }\n return false\n}\n\n// \u2500\u2500 evalExpr \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Evaluate a JS expression string against a state object.\n *\n * Results are cached by expression string \u2014 repeated evaluations hit the cache.\n * Uses a fast-path for simple dot-paths (e.g. \"count\", \"user.name\") that avoids\n * Function() overhead.\n *\n * @example\n * evalExpr('count > 0', { count: 5 }) // \u2192 true\n * evalExpr('user.name', { user: { name: 'Alice' } }) // \u2192 'Alice'\n * evalExpr('price * qty', { price: 9.99, qty: 3 }) // \u2192 29.97\n */\nexport function evalExpr(expr: string, state: StateRecord): unknown {\n // Fast-path: simple property access \u2014 no Function() needed.\n // Still guarded so bare access to Object.prototype names returns undefined.\n if (SIMPLE_PATH.test(expr)) {\n const parts = expr.split('.')\n if (!safeStateHas(state, parts[0]!)) return undefined\n return parts.reduce<unknown>((obj, key) =>\n obj != null ? (obj as StateRecord)[key] : undefined,\n state,\n )\n }\n\n if (!exprCache.has(expr)) {\n try {\n // Two with() statements: $s wins for state keys; $safe shadows globals.\n exprCache.set(\n expr,\n new Function('$s', '$safe', `with($safe){with($s){return (${expr})}}`) as Compiled,\n )\n } catch {\n warn(`invalid expression \"${expr}\"`)\n exprCache.set(expr, () => undefined)\n }\n }\n\n try {\n return exprCache.get(expr)!(safeStateWrap(state), SAFE_OUTER)\n } catch (e) {\n if (!warnedRuntime.has(expr)) {\n warnedRuntime.add(expr)\n warn(`runtime error in \"${expr}\": ${(e as Error).message}`)\n }\n return undefined\n }\n}\n\n// \u2500\u2500 Dev warnings \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** @internal Consistent warning prefix. */\nexport function warn(msg: string): void {\n console.warn(`[Micra] ${msg}`)\n}\n", "/**\n * src/core/bus.ts \u2014 Global event bus.\n *\n * Responsibilities:\n * - Publish events (emit)\n * - Subscribe and unsubscribe (on / off)\n * - Provide unsubscribe tokens for component cleanup\n *\n * LLM NOTE: The bus is a module-level singleton.\n * Component instances subscribe via `instance.on()` which auto-registers\n * the unsub token in `instance.__micraSubs` for cleanup on destroy().\n */\n\nimport type { EventHandler, UnsubFn } from '../types'\n\n// Module-level bus state \u2014 one bus per page load.\nconst _bus = new Map<string, Set<EventHandler>>()\n\n/**\n * Subscribe to a named event. Returns an unsubscribe function.\n *\n * @example\n * const unsub = on('user:login', (user) => console.log(user))\n * unsub() // stop listening\n */\nexport function on<T = unknown>(event: string, handler: EventHandler<T>): UnsubFn {\n if (!_bus.has(event)) _bus.set(event, new Set())\n _bus.get(event)!.add(handler as EventHandler)\n return () => off(event, handler as EventHandler)\n}\n\n/**\n * Unsubscribe a specific handler from an event.\n */\nexport function off(event: string, handler: EventHandler): void {\n const set = _bus.get(event)\n if (!set) return\n set.delete(handler)\n if (set.size === 0) _bus.delete(event)\n}\n\n/**\n * Publish an event to all subscribers. Errors are caught per-handler.\n *\n * @example\n * emit('user:updated', { id: 1, name: 'Alice' })\n */\nexport function emit(event: string, payload?: unknown): void {\n _bus.get(event)?.forEach(h => {\n try { h(payload) } catch (e) { console.error(`[Micra] bus error [${event}]:`, e) }\n })\n}\n", "/**\n * src/core/reactive.ts \u2014 Reactive state proxy and batch scheduler.\n *\n * Responsibilities:\n * - Wrap a plain state object in a Proxy that notifies on writes\n * - Batch multiple synchronous mutations into a single microtask render\n *\n * LLM NOTE: Both functions are PURE constructors \u2014 they have no side effects\n * beyond setting up a Proxy / Promise chain. No DOM access here.\n */\n\nimport type { StateRecord } from '../types'\n\n/**\n * Wrap `obj` in a shallow Proxy. Any property write calls `schedule()`.\n * Arrays: replace, don't mutate \u2014 `state.items = [...state.items, x]`.\n *\n * @example\n * const raw = { count: 0 }\n * const state = createReactiveState(raw, render)\n * state.count = 5 // triggers render() in next microtask\n */\nexport function createReactiveState<S extends StateRecord>(obj: S, schedule: () => void): S {\n return new Proxy(obj, {\n set(target, key: string, value: unknown) {\n // Cast through StateRecord \u2014 TypeScript cannot write through a generic index\n ;(target as StateRecord)[key] = value\n schedule()\n return true\n },\n })\n}\n\n/**\n * Return a debounce function that defers `render` to the next microtask.\n * Multiple calls within the same tick collapse to a single render.\n *\n * @example\n * const schedule = createScheduler(render)\n * schedule() // defers render\n * schedule() // no-op \u2014 already pending\n */\nexport function createScheduler(render: () => void): () => void {\n let pending = false\n return function schedule() {\n if (pending) return\n pending = true\n Promise.resolve().then(() => { pending = false; render() })\n }\n}\n", "/**\n * src/dom/query.ts \u2014 DOM query helpers.\n *\n * LLM NOTE: These are utility functions with no side effects.\n * queryOwn is the critical function that prevents a parent component from\n * accidentally processing directives belonging to a nested child component.\n */\n\n/**\n * querySelectorAll wrapper \u2014 returns a typed array.\n */\nexport function queryAll(root: ParentNode, sel: string): Element[] {\n return Array.from(root.querySelectorAll(sel))\n}\n\n/**\n * Like querySelectorAll, but EXCLUDES elements that live inside a nested\n * `[data-component]` subtree.\n *\n * This is what prevents a parent component's render() from clobbering\n * the DOM managed by a child component.\n *\n * LLM NOTE: The walk goes up parentElement until it hits `root` or null.\n * If any ancestor (between el and root) has data-component, the element is\n * owned by that nested component, not by root's component \u2014 so we skip it.\n */\nexport function queryOwn(root: Element, attr: string): Element[] {\n return queryAll(root, `[${attr}]`).filter(el => {\n let node: Element | null = el.parentElement\n while (node && node !== root) {\n if (node.hasAttribute('data-component')) return false\n node = node.parentElement\n }\n return true\n })\n}\n", "/**\n * src/dom/directives.ts \u2014 Apply DOM directives to a component subtree.\n *\n * Responsibilities:\n * - data-text, data-html, data-if, data-show, data-bind, data-model\n * - data-class (additive class toggling)\n * - Directive result cache (built once per element, reused on re-renders)\n *\n * LLM NOTE: applyDirectives() is called on every render. The directive cache\n * (DirectiveCache on el.__micraCache) avoids repeated querySelectorAll on\n * re-renders \u2014 cache is built lazily on the first call for each root element.\n *\n * Important: this module does NOT handle data-each \u2014 see dom/each.ts.\n */\n\nimport type {\n CachedBinding,\n CachedPairBinding,\n DirectiveCache,\n InternalInstance,\n MicraElement,\n MicraTemplate,\n StateRecord,\n} from '../types'\nimport { evalExpr, warn } from '../utils/expr'\nimport { queryOwn, queryAll } from './query'\n\n// \u2500\u2500 Directive appliers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Each function is PURE relative to state \u2014 reads state, writes DOM.\n\nfunction applyText(el: Element, expr: string, state: StateRecord): void {\n const text = String(evalExpr(expr, state) ?? '')\n if (el.textContent !== text) el.textContent = text\n}\n\n/**\n * data-html \u2014 writes the expression value as innerHTML.\n *\n * \u26A0\uFE0F XSS WARNING: the value is rendered as raw HTML. Never bind untrusted\n * input here \u2014 use `data-text` (textContent) instead. See docs/directives.md\n * for the full security model.\n */\nfunction applyHtml(el: Element, expr: string, state: StateRecord): void {\n el.innerHTML = String(evalExpr(expr, state) ?? '')\n}\n\nfunction applyIf(el: Element, expr: string, state: StateRecord): void {\n (el as HTMLElement).style.display = evalExpr(expr, state) ? '' : 'none'\n}\n\nfunction applyBind(\n el: Element,\n pairs: ReadonlyArray<readonly [string, string]>,\n state: StateRecord,\n): void {\n for (const [attr, valExpr] of pairs) {\n const val = evalExpr(valExpr, state)\n\n if (attr === 'class') {\n (el as HTMLElement).className = String(val ?? '')\n } else if (attr === 'value') {\n if (document.activeElement !== el)\n (el as HTMLInputElement).value = String(val ?? '')\n } else if (attr === 'style') {\n if (typeof val === 'object' && val !== null) {\n Object.assign((el as HTMLElement).style, val)\n } else {\n el.setAttribute('style', String(val ?? ''))\n }\n } else if (typeof val === 'boolean') {\n val ? el.setAttribute(attr, '') : el.removeAttribute(attr)\n } else {\n val == null ? el.removeAttribute(attr) : el.setAttribute(attr, String(val))\n }\n }\n}\n\n/**\n * data-class=\"active:isActive, disabled:count === 0\"\n * Parses comma-separated `className:expression` pairs and toggles classes additively.\n * Unlike data-bind=\"class:expr\" this does NOT replace the full className.\n *\n * Syntax mirrors data-bind \u2014 split by comma, then by first colon.\n *\n * @example\n * <div data-class=\"active:tab === 'home', hidden:!loaded\">\n */\nfunction applyClass(\n el: Element,\n pairs: ReadonlyArray<readonly [string, string]>,\n state: StateRecord,\n): void {\n for (const [cls, valExpr] of pairs) {\n el.classList.toggle(cls, Boolean(evalExpr(valExpr, state)))\n }\n}\n\n/** @internal Parse a comma+colon spec like `href:url, disabled:loading` once. */\nfunction parsePairs(expr: string): Array<readonly [string, string]> {\n const out: Array<readonly [string, string]> = []\n for (const part of expr.split(',')) {\n const colonIdx = part.indexOf(':')\n if (colonIdx === -1) continue\n const left = part.slice(0, colonIdx).trim()\n const right = part.slice(colonIdx + 1).trim()\n if (!left) continue\n out.push([left, right])\n }\n return out\n}\n\nfunction applyModel(\n el: Element,\n key: string,\n rawState: StateRecord,\n): void {\n const html = el as HTMLInputElement\n const stateVal = rawState[key]\n const desired = stateVal == null ? '' : String(stateVal)\n // Only write when out of sync. This is a no-op during live typing (the input\n // event already drove state to match el.value) but still propagates\n // programmatic resets such as `this.state.q = ''` on focused inputs.\n if (html.value !== desired) html.value = desired\n // listener is attached separately in events.ts \u2014 this only syncs the value\n}\n\n// \u2500\u2500 Directive cache \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** @internal Collect all directive bindings for a root element. Built once. */\nfunction buildCache(root: Element): DirectiveCache {\n const pick = (attr: string): CachedBinding[] => {\n const els = queryOwn(root, attr)\n // Include root itself\n if ((root as HTMLElement).hasAttribute?.(attr)) els.unshift(root)\n return els\n .filter(el => !el.closest('template'))\n .map(el => ({ el, expr: el.getAttribute(attr)! }))\n }\n const pickPairs = (attr: string): CachedPairBinding[] =>\n pick(attr).map(b => ({ ...b, pairs: parsePairs(b.expr) }))\n return {\n text: pick('data-text'),\n html: pick('data-html'),\n if: pick('data-if'),\n show: pick('data-show'),\n bind: pickPairs('data-bind'),\n model: pick('data-model'),\n class: pickPairs('data-class'),\n }\n}\n\n// \u2500\u2500 Main entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Apply all non-each directives to a component subtree.\n *\n * For regular Elements: directive bindings are cached in `el.__micraCache`\n * after the first call \u2014 subsequent re-renders skip querySelectorAll entirely.\n *\n * For DocumentFragments (no-key each clones): always re-scan because these\n * fragments are new clones on every render.\n *\n * @param root - Component root Element or DocumentFragment (no-key each clone)\n * @param state - Expression state (may include item/index for each rows)\n * @param rawState - Raw (non-proxy) state for model sync\n * @param instance - Component instance (unused here, kept for future hooks)\n */\nexport function applyDirectives<S extends StateRecord>(\n root: Element | DocumentFragment,\n state: StateRecord,\n rawState: StateRecord,\n _instance: InternalInstance<S>,\n): void {\n // DocumentFragments are temporary clones \u2014 always scan, never cache\n if (root.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {\n applyFromList(buildFragmentList(root as DocumentFragment), state, rawState)\n return\n }\n\n const el = root as MicraElement\n if (!el.__micraCache) el.__micraCache = buildCache(el)\n applyFromList(el.__micraCache, state, rawState)\n}\n\n/** @internal Apply a pre-built cache / binding list to current state. */\nfunction applyFromList(\n cache: DirectiveCache,\n state: StateRecord,\n rawState: StateRecord,\n): void {\n cache.text.forEach(b => applyText(b.el, b.expr, state))\n cache.html.forEach(b => applyHtml(b.el, b.expr, state))\n cache.if.forEach(b => applyIf(b.el, b.expr, state))\n cache.show.forEach(b => applyIf(b.el, b.expr, state))\n cache.bind.forEach(b => applyBind(b.el, b.pairs, state))\n cache.model.forEach(b => applyModel(b.el, b.expr.trim(), rawState))\n cache.class.forEach(b => applyClass(b.el, b.pairs, state))\n}\n\n/** @internal Scan a DocumentFragment (no-key each clone) \u2014 returns a DirectiveCache. */\nfunction buildFragmentList(frag: DocumentFragment): DirectiveCache {\n const pick = (attr: string): CachedBinding[] =>\n queryAll(frag, `[${attr}]`)\n .filter(el => !el.closest('template'))\n .map(el => ({ el, expr: el.getAttribute(attr)! }))\n const pickPairs = (attr: string): CachedPairBinding[] =>\n pick(attr).map(b => ({ ...b, pairs: parsePairs(b.expr) }))\n return {\n text: pick('data-text'),\n html: pick('data-html'),\n if: pick('data-if'),\n show: pick('data-show'),\n bind: pickPairs('data-bind'),\n model: pick('data-model'),\n class: pickPairs('data-class'),\n }\n}\n\n// \u2500\u2500 Dev warning helper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Validate directive usage and emit dev warnings.\n * Called once after the initial render of a component.\n *\n * @internal\n */\nexport function validateDirectives(root: Element): void {\n queryOwn(root, 'data-each').forEach(el => {\n const tmpl = el as MicraTemplate\n if (!el.hasAttribute('data-key') && !tmpl.__micraNoKeyWarned) {\n tmpl.__micraNoKeyWarned = true\n warn(`data-each=\"${el.getAttribute('data-each')}\" has no data-key \u2014 keyed diff disabled. Add data-key=\"id\" for better performance.`)\n }\n })\n\n // data-bind=\"class:...\" replaces className wholesale, which fights with\n // data-class on the same element. Warn so the developer picks one.\n const bindEls = queryOwn(root, 'data-bind')\n if ((root as HTMLElement).hasAttribute?.('data-bind') && !bindEls.includes(root)) bindEls.unshift(root)\n for (const el of bindEls) {\n const spec = el.getAttribute('data-bind') ?? ''\n const hasClassBind = spec.split(',').some(p => p.trim().split(':')[0]?.trim() === 'class')\n if (hasClassBind && el.hasAttribute('data-class')) {\n warn(`element has both data-bind=\"class:...\" and data-class \u2014 they fight on every render. Use one.`)\n }\n }\n}\n\n// Re-export warn for use in other modules\nexport { warn }\n", "/**\n * src/dom/events.ts \u2014 DOM event binding.\n *\n * Responsibilities:\n * - Bind `data-on=\"event:method\"` listeners (once per element)\n * - Bind `@event=\"method\"` shorthand (once per element)\n * - Bind `data-model` two-way input listeners (once per element)\n *\n * LLM NOTE: Every listener attached here is also recorded in\n * instance.__micraListeners so destroy() can remove it cleanly.\n * Re-render skips already-bound elements via per-element __micra* flags.\n */\n\nimport type { InternalInstance, MicraElement, StateRecord } from '../types'\nimport { warn } from '../utils/expr'\nimport { queryOwn, queryAll } from './query'\n\n/** @internal Attach a DOM listener and track it on the instance for destroy(). */\nfunction track<S extends StateRecord>(\n instance: InternalInstance<S>,\n el: Element,\n type: string,\n fn: EventListener,\n): void {\n el.addEventListener(type, fn)\n ;(instance.__micraListeners ??= []).push({ el, type, fn })\n}\n\n// \u2500\u2500 data-on \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Bind `data-on=\"event:method[,event2:method2]\"` listeners.\n * Listeners are bound once \u2014 re-render calls are no-ops for already-bound elements.\n *\n * Supports modifiers: `click.prevent`, `click.stop`, `click.self`.\n *\n * @example\n * <button data-on=\"click:save\">Save</button>\n * <form data-on=\"submit.prevent:handleSubmit\">\n */\nexport function bindDataOn<S extends StateRecord>(\n root: Element,\n instance: InternalInstance<S>,\n): void {\n const isFragment = root.nodeType === 11\n const els = isFragment\n ? queryAll(root as unknown as ParentNode, '[data-on]')\n : queryOwn(root, 'data-on')\n\n // Include root itself if it carries data-on (e.g., the keyed item IS the button)\n if (!isFragment && (root as HTMLElement).hasAttribute?.('data-on') && !els.includes(root))\n els.unshift(root)\n\n for (const el of els) {\n const mEl = el as MicraElement\n if (mEl.__micraEvents) continue\n mEl.__micraEvents = true\n\n const spec = mEl.dataset['on'] ?? ''\n for (const part of spec.split(',')) {\n const [evSpec, method] = part.trim().split(':') as [string, string]\n if (!evSpec || !method) continue\n\n const [evName, ...mods] = evSpec.split('.')\n\n track(instance, el, evName!, (e: Event) => {\n if (mods.includes('prevent')) e.preventDefault()\n if (mods.includes('stop')) e.stopPropagation()\n if (mods.includes('self') && e.target !== el) return\n\n const fn = instance[method.trim()]\n if (typeof fn === 'function') (fn as (e: Event) => void).call(instance, e)\n else warn(`method \"${method.trim()}\" not found`)\n })\n }\n }\n}\n\n// \u2500\u2500 @event shorthand \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Bind `@event=\"method\"` shorthand attributes (Stimulus-style).\n * Bound once per element via `__micraAtBound` \u2014 re-renders are no-ops.\n * Supports the same modifiers as data-on: `@click.prevent=\"submit\"`.\n *\n * @example\n * <button @click=\"increment\">+</button>\n * <form @submit.prevent=\"handleSubmit\">\n */\nexport function bindAtEvents<S extends StateRecord>(\n root: Element,\n instance: InternalInstance<S>,\n): void {\n const isFragment = root.nodeType === 11\n const all = isFragment\n ? queryAll(root as unknown as ParentNode, '*')\n : queryAll(root, '*')\n\n // Include root itself for the regular-element case\n if (!isFragment && !all.includes(root)) all.unshift(root)\n\n for (const el of all) {\n const mEl = el as MicraElement\n if (mEl.__micraAtBound) continue\n\n let bound = false\n for (const attr of Array.from(el.attributes)) {\n if (!attr.name.startsWith('@')) continue\n const [evSpec, ...rest] = attr.name.slice(1).split('.')\n const method = attr.value.trim()\n\n track(instance, el, evSpec!, (e: Event) => {\n if (rest.includes('prevent')) e.preventDefault()\n if (rest.includes('stop')) e.stopPropagation()\n if (rest.includes('self') && e.target !== el) return\n\n const fn = instance[method]\n if (typeof fn === 'function') (fn as (e: Event) => void).call(instance, e)\n else warn(`method \"${method}\" not found`)\n })\n bound = true\n }\n if (bound) mEl.__micraAtBound = true\n }\n}\n\n// \u2500\u2500 data-model \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Two-way binding: `data-model=\"key\"` wires <input>/<select>/<textarea>\n * to `state[key]`. Binding is attached once per element.\n *\n * Numeric inputs (`type=\"number\"` / `type=\"range\"`) write numbers, not strings.\n * Checkbox inputs write booleans. Everything else writes strings.\n *\n * @example\n * <input data-model=\"search\"> // updates state.search on every keystroke\n * <select data-model=\"sortBy\"> // updates state.sortBy on change\n */\nexport function bindModels<S extends StateRecord>(\n root: Element,\n instance: InternalInstance<S>,\n): void {\n const isFragment = root.nodeType === 11\n const els = isFragment\n ? queryAll(root as unknown as ParentNode, '[data-model]')\n : queryOwn(root, 'data-model')\n\n for (const el of els) {\n const mEl = el as MicraElement\n if (mEl.__micraModel) continue\n mEl.__micraModel = true\n\n const key = (el as HTMLInputElement).dataset['model'] ?? ''\n const tag = el.tagName\n const inputEl = el as HTMLInputElement\n const inputType = inputEl.type\n\n const update = () => {\n let val: unknown\n if (tag === 'INPUT' && inputType === 'checkbox') {\n val = inputEl.checked\n } else if (tag === 'INPUT' && (inputType === 'number' || inputType === 'range')) {\n // Empty string \u2192 NaN; preserve raw empty as null so state stays \"unfilled\"\n val = inputEl.value === '' ? null : inputEl.valueAsNumber\n } else {\n val = inputEl.value\n }\n ;(instance.state as StateRecord)[key] = val\n }\n\n const evType = tag === 'SELECT' || inputType === 'radio' ? 'change' : 'input'\n track(instance, el, evType, update)\n }\n}\n", "/**\n * src/dom/each.ts \u2014 Keyed and non-keyed list rendering (data-each).\n *\n * Responsibilities:\n * - Process `<template data-each=\"items\" data-key=\"id\">` elements\n * - Keyed diff: reuse/reorder DOM nodes by key \u2014 O(n) with a Map\n * - Non-keyed fallback: full replace (no key \u2192 warn in dev, full re-render)\n * - Apply directives to each row with a scoped itemState\n *\n * LLM NOTE: renderList() is called on every render cycle AFTER applyDirectives().\n * Only <template> elements with data-each are processed.\n * Keyed mode (data-key present) mutates the DOM in-place \u2014 nodes are\n * created once and reused. Non-keyed mode removes all nodes and re-clones.\n */\n\nimport type { InternalInstance, MicraElement, MicraTemplate, StateRecord } from '../types'\nimport { evalExpr, warn } from '../utils/expr'\nimport { applyDirectives } from './directives'\nimport { bindDataOn, bindAtEvents } from './events'\nimport { queryOwn, queryAll } from './query'\n\n/**\n * Process all `<template data-each>` elements owned by `root`.\n * Scoped itemState makes `item`, `index`, `$index` available in row expressions.\n *\n * @param root - Component root Element\n * @param state - Expression state (proxy merging rawState + instance)\n * @param rawState - Raw (non-proxy) state \u2014 used for model binding\n * @param instance - Component instance (for event binding)\n */\nexport function renderList<S extends StateRecord>(\n root: Element,\n state: StateRecord,\n rawState: StateRecord,\n instance: InternalInstance<S>,\n): void {\n queryOwn(root, 'data-each').forEach(tmplEl => {\n if (tmplEl.tagName !== 'TEMPLATE') return\n const tmpl = tmplEl as MicraTemplate\n\n const itemsExpr = tmpl.getAttribute('data-each')!\n const keyAttr = tmpl.getAttribute('data-key') ?? null\n const items = evalExpr(itemsExpr, state)\n\n // Ensure marker comment + internal state are initialized\n if (!tmpl.__micraMarker) {\n const m = document.createComment(`each:${itemsExpr}`)\n tmpl.after(m)\n tmpl.__micraMarker = m\n tmpl.__micraNodes = new Map()\n tmpl.__micraList = []\n }\n\n const marker = tmpl.__micraMarker\n const keyMap = tmpl.__micraNodes\n const parent = marker.parentNode!\n\n // Empty / non-array: clear all rendered rows\n if (!Array.isArray(items)) {\n tmpl.__micraList.forEach(n => n.remove())\n tmpl.__micraList = []\n keyMap.clear()\n return\n }\n\n if (keyAttr) {\n renderKeyed(tmpl, items as StateRecord[], keyAttr, marker, keyMap, parent, state, rawState, instance)\n } else {\n renderNoKey(tmpl, items as StateRecord[], marker, parent, state, rawState, instance)\n }\n })\n}\n\n// \u2500\u2500 Keyed diff \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction renderKeyed<S extends StateRecord>(\n tmpl: MicraTemplate,\n items: StateRecord[],\n keyAttr: string,\n marker: Comment,\n keyMap: Map<unknown, MicraElement>,\n parent: Node,\n state: StateRecord,\n rawState: StateRecord,\n instance: InternalInstance<S>,\n): void {\n const nextKeys = new Set<unknown>()\n const nextNodes: MicraElement[] = []\n let warnedNullKey = false\n let warnedDupKey = false\n\n for (const [index, item] of items.entries()) {\n const key = item[keyAttr]\n if (key == null && !warnedNullKey) {\n warn(`data-key=\"${keyAttr}\" is null/undefined on item at index ${index}`)\n warnedNullKey = true\n }\n if (nextKeys.has(key) && !warnedDupKey) {\n warn(`data-key=\"${keyAttr}\" has duplicate value ${JSON.stringify(key)} \u2014 rows will collide`)\n warnedDupKey = true\n }\n nextKeys.add(key)\n\n let node = keyMap.get(key) as MicraElement | undefined\n\n if (!node) {\n // Clone template and wrap multi-root fragments in a display:contents element\n const frag = tmpl.content.cloneNode(true) as DocumentFragment\n if (frag.childNodes.length === 1) {\n node = frag.firstElementChild as MicraElement\n } else {\n node = document.createElement('micra-each-item') as MicraElement\n node.style.display = 'contents'\n node.append(frag)\n }\n node.__micraKey = key\n keyMap.set(key, node)\n // Bind data-on and @event handlers on the freshly created node (once)\n bindDataOn(node, instance)\n bindAtEvents(node, instance)\n }\n\n const itemState = Object.assign(\n Object.create(state) as StateRecord,\n { item, index, $index: index },\n )\n applyDirectives(node, itemState, rawState, instance)\n nextNodes.push(node)\n }\n\n // Remove stale nodes\n for (const [key, node] of keyMap) {\n if (!nextKeys.has(key)) { node.remove(); keyMap.delete(key) }\n }\n\n // Insert / reorder nodes after marker (insertBefore is no-op if already in place)\n let cursor: Node = marker\n for (const node of nextNodes) {\n if (cursor.nextSibling !== node) parent.insertBefore(node, cursor.nextSibling)\n cursor = node\n }\n\n tmpl.__micraList = nextNodes\n}\n\n// \u2500\u2500 Non-keyed (full re-render) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction renderNoKey<S extends StateRecord>(\n tmpl: MicraTemplate,\n items: StateRecord[],\n marker: Comment,\n parent: Node,\n state: StateRecord,\n rawState: StateRecord,\n instance: InternalInstance<S>,\n): void {\n tmpl.__micraList.forEach(n => n.remove())\n tmpl.__micraList = []\n\n const frag = document.createDocumentFragment()\n for (const [index, item] of items.entries()) {\n const clone = tmpl.content.cloneNode(true) as DocumentFragment\n const itemState = Object.assign(\n Object.create(state) as StateRecord,\n { item, index, $index: index },\n )\n applyDirectives(clone, itemState, rawState, instance)\n bindDataOn(clone as unknown as Element, instance)\n bindAtEvents(clone as unknown as Element, instance)\n\n const nodes = Array.from(clone.childNodes) as MicraElement[]\n nodes.forEach(n => { n.__micraEach = true; frag.append(n) })\n tmpl.__micraList.push(...nodes)\n }\n parent.insertBefore(frag, marker.nextSibling)\n}\n", "/**\n * src/dom/refs.ts \u2014 data-ref collection.\n *\n * Responsibilities:\n * - After each render, scan for `[data-ref]` elements (owned by this component)\n * - Populate `instance.refs` so methods can do `this.refs.chart` etc.\n *\n * LLM NOTE: This module is PURE relative to state \u2014 it only reads DOM attributes\n * and writes to instance.refs. It does NOT trigger renders.\n */\n\nimport type { InternalInstance, MicraElement, StateRecord } from '../types'\nimport { queryOwn } from './query'\n\n/**\n * Collect all `[data-ref=\"name\"]` elements owned by this component root into\n * `instance.refs`.\n *\n * Called once after the initial render and again on every re-render (refs may\n * point to newly created elements after an each-list update).\n *\n * @example\n * // HTML: <canvas data-ref=\"chart\">\n * // JS: this.refs.chart \u2192 HTMLCanvasElement\n */\nexport function collectRefs<S extends StateRecord>(\n root: Element,\n instance: InternalInstance<S>,\n): void {\n instance.refs = {}\n for (const el of queryOwn(root, 'data-ref')) {\n const name = (el as MicraElement).dataset['ref']\n if (name) instance.refs[name] = el as HTMLElement\n }\n}\n", "/**\n * src/core/mount.ts \u2014 Mount a component definition onto a DOM element.\n *\n * Responsibilities:\n * - Create and initialize an InternalInstance\n * - Set up reactive state + batch scheduler\n * - Wire render(), destroy(), prop(), fetch(), on(), emit()\n * - Run initial render + call onCreate() in a microtask\n *\n * LLM NOTE: This is the core of the Micra runtime.\n * mount() is called by both the public Micra.mount() API and by start()\n * (which scans the DOM for [data-component] elements).\n */\n\nimport type {\n ComponentDefinition,\n ComponentInstance,\n EventHandler,\n InternalInstance,\n MicraElement,\n StateRecord,\n UnsubFn,\n} from '../types'\nimport { warn } from '../utils/expr'\nimport { micraFetch } from '../utils/fetch'\nimport { on as busOn, emit as busEmit } from '../core/bus'\nimport { createReactiveState, createScheduler } from '../core/reactive'\nimport { applyDirectives, validateDirectives } from '../dom/directives'\nimport { renderList } from '../dom/each'\nimport { bindDataOn, bindAtEvents, bindModels } from '../dom/events'\nimport { collectRefs } from '../dom/refs'\nimport { _instances } from '../core/registry'\n\n/**\n * Mount a component definition onto a DOM element.\n * Returns the component instance, or null if the root element is not found.\n *\n * Already-mounted elements return the existing instance.\n *\n * @example\n * const instance = Micra.mount('#counter', {\n * state: { count: 0 },\n * inc() { this.state.count++ },\n * })\n */\nexport function mount<S extends StateRecord>(\n selector: string | HTMLElement,\n definition: ComponentDefinition<S>,\n): ComponentInstance<S> | null {\n const root =\n typeof selector === 'string'\n ? document.querySelector<HTMLElement>(selector)\n : selector\n\n if (!root) {\n warn(`\"${selector}\" not found`)\n return null\n }\n\n // Already mounted \u2014 return existing instance without re-mounting\n if (_instances.has(root)) return _instances.get(root) as ComponentInstance<S>\n\n const rawState: StateRecord = { ...(definition.state ?? {}) }\n const instance = { $el: root, refs: {} } as InternalInstance<S>\n\n // Copy user-defined methods from definition to instance\n for (const [key, val] of Object.entries(definition as Record<string, unknown>)) {\n if (key === 'state' || key === 'onCreate' || key === 'onDestroy') continue\n if (typeof val === 'function') instance[key] = val\n }\n\n // \u2500\u2500 prop() \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Read data-* attributes from the root element with auto-cast.\n instance.prop = function <T>(name: string, defaultVal?: T): T | undefined {\n const val = root.dataset[name]\n if (val === undefined) return defaultVal\n if (val === 'true') return true as unknown as T\n if (val === 'false') return false as unknown as T\n if (val !== '' && !isNaN(Number(val))) return Number(val) as unknown as T\n return val as unknown as T\n } as ComponentInstance<S>['prop']\n\n // \u2500\u2500 fetch(), emit(), on() \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n instance.fetch = micraFetch\n instance.emit = busEmit\n\n instance.on = <T = unknown>(event: string, handler: EventHandler<T>): UnsubFn => {\n const unsub = busOn(event, handler)\n if (!instance.__micraSubs) instance.__micraSubs = []\n instance.__micraSubs.push(unsub)\n return unsub\n }\n\n // \u2500\u2500 Render \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n let isRendering = false\n const schedule = createScheduler(() => instance.render())\n instance.state = createReactiveState(rawState, schedule) as S\n\n // Expression state: proxy that falls back to instance methods so expressions\n // like `data-text=\"formatDate(item.date)\"` can call component methods.\n //\n // Instance methods are returned BOUND to the instance \u2014 directive expressions\n // call them as bare identifiers via `with()`, which would normally lose `this`.\n // Bound copies are memoized per method name so repeated reads are cheap.\n //\n // Both traps reject Object.prototype names ('constructor', 'toString', ...) \u2014\n // accessing them via a directive expression returns undefined instead of\n // leaking the prototype.\n const boundMethods = new Map<string, Function>()\n const exprState = new Proxy(rawState, {\n get(target, key: string) {\n if (Object.prototype.hasOwnProperty.call(target, key)) return target[key]\n if (Object.prototype.hasOwnProperty.call(instance, key) &&\n typeof instance[key] === 'function') {\n const cached = boundMethods.get(key)\n if (cached) return cached\n const bound = (instance[key] as Function).bind(instance)\n boundMethods.set(key, bound)\n return bound\n }\n return undefined\n },\n has(target, key: string) {\n if (typeof key !== 'string') return false\n if (Object.prototype.hasOwnProperty.call(target, key)) return true\n return Object.prototype.hasOwnProperty.call(instance, key) &&\n typeof instance[key] === 'function'\n },\n })\n\n let warnedReentry = false\n instance.render = function () {\n if (instance.__micraDestroyed) return\n if (isRendering) {\n if (!warnedReentry) {\n warn('render() re-entry detected \u2014 mutation inside a directive expression is ignored. Move state writes to a method.')\n warnedReentry = true\n }\n return\n }\n isRendering = true\n try {\n applyDirectives(root, exprState, rawState, instance)\n renderList(root, exprState, rawState, instance)\n bindDataOn(root, instance)\n bindAtEvents(root, instance)\n bindModels(root, instance)\n collectRefs(root, instance)\n } finally {\n isRendering = false\n }\n }\n\n // \u2500\u2500 Destroy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n instance.destroy = function () {\n if (instance.__micraDestroyed) return\n instance.__micraDestroyed = true\n\n // Remove every DOM listener attached by bindDataOn / bindAtEvents / bindModels.\n instance.__micraListeners?.forEach(({ el, type, fn }) => el.removeEventListener(type, fn))\n instance.__micraListeners = []\n\n // Clear per-element flags & cached directive scan so a future re-mount of the same DOM works.\n const clearFlags = (el: Element) => {\n const m = el as MicraElement\n delete m.__micraEvents\n delete m.__micraAtBound\n delete m.__micraModel\n delete m.__micraCache\n }\n clearFlags(root)\n root.querySelectorAll('*').forEach(clearFlags)\n\n instance.__micraSubs?.forEach(unsub => unsub())\n instance.__micraSubs = []\n\n if (typeof (definition as Record<string, unknown>).onDestroy === 'function')\n (definition.onDestroy as () => void).call(instance)\n _instances.delete(root)\n }\n\n // \u2500\u2500 Bootstrap \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n _instances.set(root, instance as InternalInstance)\n instance.render()\n\n // Validate directive usage and emit dev warnings\n validateDirectives(root)\n\n if (typeof (definition as Record<string, unknown>).onCreate === 'function')\n Promise.resolve().then(() =>\n (definition.onCreate as () => void | Promise<void>).call(instance),\n )\n\n return instance\n}\n", "/**\n * src/core/start.ts \u2014 Auto-mount via [data-component].\n *\n * Responsibilities:\n * - Scan the DOM (or a subtree) for [data-component] elements\n * - Mount each using the registered definition\n * - Skip already-mounted elements (safe to call multiple times)\n * - Warn clearly when a component name is not registered\n *\n * LLM NOTE: start() is SSR-friendly \u2014 calling it multiple times is safe\n * because mount() checks _instances before re-mounting.\n */\n\nimport { warn } from '../utils/expr'\nimport { _registry, _instances } from './registry'\nimport { mount } from './mount'\n\n/**\n * Scan for `[data-component]` elements and auto-mount registered definitions.\n *\n * Pass a subtree root to limit the scan (e.g., after a partial SSR update):\n * `Micra.start(document.getElementById('panel'))`\n *\n * @example\n * // Mount everything on the page (called once after DOM ready)\n * Micra.start()\n *\n * // Re-mount after injecting new HTML\n * Micra.start(document.querySelector('#dynamic-section'))\n */\nexport function start(root: Document | HTMLElement = document): void {\n root.querySelectorAll<HTMLElement>('[data-component]').forEach(el => {\n if (_instances.has(el)) return // already mounted \u2014 skip\n const name = el.getAttribute('data-component')!\n const def = _registry.get(name)\n if (!def) {\n warn(`component \"${name}\" not defined. Call Micra.define('${name}', {...}) first.`)\n return\n }\n mount(el, def)\n })\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Micra.js \u2014 Lightweight reactive framework for small sites and simple SaaS.\n *\n * Public surface \u2014 re-exports only.\n *\n * Features:\n * - JS expressions in directives (data-if=\"count > 0\")\n * - Keyed list diffing (data-each=\"items\" data-key=\"id\")\n * - Auto-mount via data-component (Micra.define + Micra.start)\n * - Props from SSR data-attributes (this.prop('page'))\n * - Built-in fetch helper (this.fetch('/api/...'))\n * - Global event bus (Micra.on / Micra.emit)\n * - DOM refs (data-ref=\"chart\" \u2192 this.refs.chart)\n * - Additive class toggling (data-class=\"active:isActive\")\n * - @event shorthand (@click=\"increment\")\n * - Lifecycle: onCreate, onDestroy\n * - SSR-friendly: Micra.start() is safe to call multiple times\n * - Directive cache: O(1) re-renders after first mount\n *\n * Size target: < 5.5 KB minified+gzipped\n *\n * @module Micra\n */\n\n// \u2500\u2500 Public types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport type {\n StateRecord,\n UnsubFn,\n EventHandler,\n FetchOptions,\n ComponentInstance,\n ComponentDefinition,\n} from './types'\n\n// \u2500\u2500 Errors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport { FetchError } from './utils/fetch'\n\n// \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport { define, defineComponent, instances, registry, debug } from './core/registry'\nexport { mount } from './core/mount'\nexport { start } from './core/start'\nexport { on, off, emit } from './core/bus'\n", "/**\n * src/utils/fetch.ts \u2014 HTTP fetch helper.\n *\n * Responsibilities:\n * - Auto-attach CSRF token from <meta name=\"csrf-token\">\n * - Serialize POST/PUT/PATCH body as JSON\n * - Serialize GET/HEAD options as query params\n * - Throw a typed FetchError on non-2xx responses\n * - Return parsed JSON or text\n *\n * LLM NOTE: This module is PURE (no DOM side effects beyond reading a meta tag).\n * It wraps the native fetch() API with SaaS-friendly defaults.\n */\n\nimport type { FetchOptions } from '../types'\n\n// \u2500\u2500 CSRF \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Read CSRF token from <meta name=\"csrf-token\"> (Rails, Laravel, Django\u2026). */\nfunction getCSRF(): string | null {\n return document.querySelector('meta[name=\"csrf-token\"]')?.getAttribute('content') ?? null\n}\n\n// \u2500\u2500 Typed error \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Thrown by `this.fetch()` when the server returns a non-2xx status.\n *\n * @example\n * try {\n * await this.fetch('/api/data')\n * } catch (e) {\n * if (e instanceof FetchError && e.status === 404) { ... }\n * }\n */\nexport class FetchError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly response: Response,\n ) {\n super(message)\n this.name = 'FetchError'\n }\n}\n\n// \u2500\u2500 Fetch helper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Fetch wrapper with SaaS defaults.\n *\n * - GET/HEAD: extra `options` keys become URL query params\n * - POST/PUT/PATCH/DELETE: `options.body` is JSON-serialized\n * - Attaches X-CSRF-Token header automatically\n * - Returns parsed JSON if Content-Type is application/json, else text\n *\n * @example\n * // GET with params \u2192 /api/users?page=2&status=active\n * const data = await this.fetch('/api/users', { page: 2, status: 'active' })\n *\n * // POST with JSON body\n * await this.fetch('/api/invite', { method: 'POST', body: { email, role } })\n */\nexport async function micraFetch(url: string, options: FetchOptions = {}): Promise<unknown> {\n const method = ((options.method as string | undefined) ?? 'GET').toUpperCase()\n const headers: Record<string, string> = {\n Accept: 'application/json',\n ...(options.headers as Record<string, string> | undefined),\n }\n\n const csrf = getCSRF()\n if (csrf) headers['X-CSRF-Token'] = csrf\n\n let finalUrl = url\n let body: string | undefined\n\n if (method === 'GET' || method === 'HEAD') {\n const params: Record<string, string> = {}\n for (const [k, v] of Object.entries(options)) {\n if (k !== 'method' && k !== 'headers' && k !== 'signal' && v != null)\n params[k] = String(v)\n }\n if (Object.keys(params).length)\n finalUrl += (url.includes('?') ? '&' : '?') + new URLSearchParams(params)\n } else if (options.body !== undefined) {\n headers['Content-Type'] = 'application/json'\n body = JSON.stringify(options.body)\n }\n\n const res = await fetch(finalUrl, {\n method,\n headers,\n ...(options.signal !== undefined ? { signal: options.signal as AbortSignal } : {}),\n ...(body !== undefined ? { body } : {}),\n })\n\n if (!res.ok)\n throw new FetchError(`[Micra] fetch: ${method} ${url} \u2192 ${res.status}`, res.status, res)\n\n const ct = res.headers.get('content-type') ?? ''\n return ct.includes('application/json') ? res.json() : res.text()\n}\n", "/**\n * src/core/registry.ts \u2014 Component definition registry and instance store.\n *\n * Responsibilities:\n * - Store named component definitions (define / registry)\n * - Store live component instances keyed by root HTMLElement (instances)\n *\n * LLM NOTE: Both maps are module-level singletons (one per page load).\n * They are intentionally mutable from mount.ts and start.ts.\n */\n\nimport type {\n ComponentDefinition,\n ComponentInstance,\n InternalInstance,\n StateRecord,\n} from '../types'\n\n// Named definition map \u2014 populated by define()\nexport const _registry = new Map<string, ComponentDefinition>()\n\n// Live instance map \u2014 populated by mount(), cleared by instance.destroy()\nexport const _instances = new Map<HTMLElement, InternalInstance>()\n\n// \u2500\u2500 Public accessors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Register a component definition under `name`.\n *\n * @example\n * define('counter', { state: { count: 0 }, inc() { this.state.count++ } })\n */\nexport function define<S extends StateRecord>(\n name: string,\n definition: ComponentDefinition<S>,\n): void {\n _registry.set(name, definition as ComponentDefinition)\n}\n\n/**\n * Type-helper \u2014 returns `definition` unchanged but lets TypeScript infer `S`\n * from the `state` literal so all methods are typed with the correct `this`.\n *\n * Use this when defining a component outside a `define()` call.\n *\n * @example\n * const counter = defineComponent({\n * state: { count: 0 },\n * increment() { this.state.count++ }, // this.state: { count: number } \u2713\n * })\n * Micra.define('counter', counter)\n */\nexport function defineComponent<S extends StateRecord>(\n definition: ComponentDefinition<S>,\n): ComponentDefinition<S> {\n return definition\n}\n\n/**\n * Returns a read-only view of all live instances (keyed by root element).\n * Useful for DevTools / debugging.\n */\nexport function instances(): ReadonlyMap<HTMLElement, ComponentInstance> {\n return _instances\n}\n\n/**\n * Returns a read-only view of all registered component definitions.\n * Useful for DevTools / debugging.\n */\nexport function registry(): ReadonlyMap<string, ComponentDefinition> {\n return _registry\n}\n\n/**\n * Print all live component instances to the browser console.\n * Shows component name, root element, and current state for each instance.\n *\n * @example\n * // In browser DevTools console:\n * Micra.debug()\n * // [Micra] 3 live component(s)\n * // counter $el: <div> state: { count: 5 }\n * // user-list $el: <div> state: { users: [...], loading: false }\n */\nexport function debug(): void {\n if (_instances.size === 0) {\n console.log('[Micra] No live components.')\n return\n }\n console.group(`[Micra] ${_instances.size} live component(s)`)\n for (const [el, instance] of _instances) {\n const name = el.getAttribute('data-component') ?? '(unnamed)'\n console.group(`%c${name}`, 'font-weight:bold;color:#6366f1')\n console.log('$el ', el)\n console.log('state', { ...instance.state })\n console.groupEnd()\n }\n console.groupEnd()\n}\n", "/**\n * src/utils/expr.ts \u2014 JS expression evaluator.\n *\n * Responsibilities:\n * - Compile expression strings into cached functions\n * - Evaluate them against a state object\n * - Fast-path for simple property lookups\n * - Shadow non-state identifiers so directive expressions cannot reach\n * globals like `window`, `fetch`, `constructor`, etc. A small whitelist\n * of utility globals (Math, JSON, Date, ...) remains accessible.\n *\n * LLM NOTE: This module is PURE. It does not touch the DOM or mutate state.\n *\n * Security model:\n * Directive expressions are JavaScript \u2014 they are compiled via `new Function`\n * and run with full JS capability except that bare identifiers must resolve\n * to either a state key, a component instance method, or one of\n * ALLOWED_GLOBALS. This blocks the `constructor.constructor(\"...\")()` chain\n * and accidental access to `window` / `document` / `fetch`. It does NOT\n * sandbox method calls \u2014 if a component method itself touches `window`,\n * that still works. Treat directive templates as trusted code regardless.\n */\n\nimport type { StateRecord } from '../types'\n\n// \u2500\u2500 Expression cache \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Compiled functions are keyed by expression string \u2014 Function() is only called\n// once per unique expression across the entire app lifetime.\n\n// LLM NOTE: exprCache is module-level (shared across all components).\n// This is intentional \u2014 most apps reuse the same expressions.\ntype Compiled = (state: object, safe: object) => unknown\nconst exprCache = new Map<string, Compiled>()\n// Expressions whose runtime error we have already warned about. Prevents log spam\n// when the same `data-text=\"item.naame\"` typo fires every render.\nconst warnedRuntime = new Set<string>()\n\n// Simple identifier or dot-path: \"count\", \"user.name\", \"item.email\"\n// Matches: letter/$/_ followed by word chars, optionally with .property chains\nconst SIMPLE_PATH = /^[a-zA-Z_$][a-zA-Z0-9_$]*(\\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/\n\n// \u2500\u2500 Safe scope \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Globals reachable from directive expressions. Anything else (window, fetch,\n * constructor, eval, ...) is shadowed by SAFE_OUTER and resolves to undefined.\n */\nconst ALLOWED_GLOBALS = new Set<string>([\n 'Math', 'JSON', 'Date', 'String', 'Number', 'Boolean', 'Array', 'Object',\n 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'NaN', 'Infinity', 'undefined',\n])\n\n/**\n * Outer `with()` scope. Its `has` trap claims every non-whitelisted identifier\n * is \"in scope\" so the JS engine resolves the read on this Proxy (which returns\n * undefined) instead of walking up to the global object. Whitelisted names fall\n * through to globalThis.\n */\n// Sentinel parameter names used by the compiled function. SAFE_OUTER must NOT\n// shadow them, or `with($s)` would resolve to `undefined` via SAFE_OUTER.\nconst PARAM_S = '$s'\nconst PARAM_SAFE = '$safe'\n\nconst SAFE_OUTER: object = new Proxy(Object.create(null) as object, {\n has(_target, key): boolean {\n if (typeof key !== 'string') return false\n if (key === PARAM_S || key === PARAM_SAFE) return false\n return !ALLOWED_GLOBALS.has(key)\n },\n get(): undefined {\n return undefined\n },\n})\n\n/**\n * @internal Per-state safe wrappers \u2014 one per source state object. WeakMap so\n * short-lived itemStates get GC'd with their wrappers.\n */\nconst safeWrapCache = new WeakMap<object, object>()\n\n/**\n * @internal Pre-computed names that live on `Object.prototype`\n * (constructor, toString, hasOwnProperty, ...). Used by safeStateHas to detect\n * built-in keys without re-walking the chain on every call.\n */\nconst OBJ_PROTO_KEYS = new Set<string>(Object.getOwnPropertyNames(Object.prototype))\n\n/**\n * Wrap a state object so its `has` trap reports only \"real\" keys \u2014 own\n * properties or keys reachable up to (but not including) `Object.prototype`.\n * This blocks `'constructor' in state` from leaking the prototype.\n */\nfunction safeStateWrap(state: object): object {\n const cached = safeWrapCache.get(state)\n if (cached) return cached\n const wrapped = new Proxy(state, {\n has(target, key) {\n return safeStateHas(target, key)\n },\n get(target, key) {\n return Reflect.get(target, key)\n },\n })\n safeWrapCache.set(state, wrapped)\n return wrapped\n}\n\n/**\n * Return true iff `key` is reachable on `state` without walking into\n * `Object.prototype`. Works for plain objects, prototype-chained objects, and\n * Proxies with their own `has` trap.\n */\nfunction safeStateHas(state: object, key: PropertyKey): boolean {\n if (typeof key !== 'string') return false\n if (!Reflect.has(state, key)) return false\n // Identifiers that are NOT on Object.prototype are always safe \u2014 accept them\n // immediately without walking the chain.\n if (!OBJ_PROTO_KEYS.has(key)) return true\n // Built-in Object.prototype names (constructor, toString, hasOwnProperty, ...)\n // are only accepted when they have been explicitly placed on the state chain.\n let obj: object | null = state\n while (obj && obj !== Object.prototype) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) return true\n obj = Object.getPrototypeOf(obj) as object | null\n }\n return false\n}\n\n// \u2500\u2500 evalExpr \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Evaluate a JS expression string against a state object.\n *\n * Results are cached by expression string \u2014 repeated evaluations hit the cache.\n * Uses a fast-path for simple dot-paths (e.g. \"count\", \"user.name\") that avoids\n * Function() overhead.\n *\n * @example\n * evalExpr('count > 0', { count: 5 }) // \u2192 true\n * evalExpr('user.name', { user: { name: 'Alice' } }) // \u2192 'Alice'\n * evalExpr('price * qty', { price: 9.99, qty: 3 }) // \u2192 29.97\n */\nexport function evalExpr(expr: string, state: StateRecord): unknown {\n // Fast-path: simple property access \u2014 no Function() needed.\n // Still guarded so bare access to Object.prototype names returns undefined.\n if (SIMPLE_PATH.test(expr)) {\n const parts = expr.split('.')\n if (!safeStateHas(state, parts[0]!)) return undefined\n return parts.reduce<unknown>((obj, key) =>\n obj != null ? (obj as StateRecord)[key] : undefined,\n state,\n )\n }\n\n if (!exprCache.has(expr)) {\n try {\n // Two with() statements: $s wins for state keys; $safe shadows globals.\n exprCache.set(\n expr,\n new Function('$s', '$safe', `with($safe){with($s){return (${expr})}}`) as Compiled,\n )\n } catch {\n warn(`invalid expression \"${expr}\"`)\n exprCache.set(expr, () => undefined)\n }\n }\n\n try {\n return exprCache.get(expr)!(safeStateWrap(state), SAFE_OUTER)\n } catch (e) {\n if (!warnedRuntime.has(expr)) {\n warnedRuntime.add(expr)\n warn(`runtime error in \"${expr}\": ${(e as Error).message}`)\n }\n return undefined\n }\n}\n\n// \u2500\u2500 Dev warnings \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** @internal Consistent warning prefix. */\nexport function warn(msg: string): void {\n console.warn(`[Micra] ${msg}`)\n}\n", "/**\n * src/core/bus.ts \u2014 Global event bus.\n *\n * Responsibilities:\n * - Publish events (emit)\n * - Subscribe and unsubscribe (on / off)\n * - Provide unsubscribe tokens for component cleanup\n *\n * LLM NOTE: The bus is a module-level singleton.\n * Component instances subscribe via `instance.on()` which auto-registers\n * the unsub token in `instance.__micraSubs` for cleanup on destroy().\n */\n\nimport type { EventHandler, UnsubFn } from '../types'\n\n// Module-level bus state \u2014 one bus per page load.\nconst _bus = new Map<string, Set<EventHandler>>()\n\n/**\n * Subscribe to a named event. Returns an unsubscribe function.\n *\n * @example\n * const unsub = on('user:login', (user) => console.log(user))\n * unsub() // stop listening\n */\nexport function on<T = unknown>(event: string, handler: EventHandler<T>): UnsubFn {\n if (!_bus.has(event)) _bus.set(event, new Set())\n _bus.get(event)!.add(handler as EventHandler)\n return () => off(event, handler as EventHandler)\n}\n\n/**\n * Unsubscribe a specific handler from an event.\n */\nexport function off(event: string, handler: EventHandler): void {\n const set = _bus.get(event)\n if (!set) return\n set.delete(handler)\n if (set.size === 0) _bus.delete(event)\n}\n\n/**\n * Publish an event to all subscribers. Errors are caught per-handler.\n *\n * @example\n * emit('user:updated', { id: 1, name: 'Alice' })\n */\nexport function emit(event: string, payload?: unknown): void {\n _bus.get(event)?.forEach(h => {\n try { h(payload) } catch (e) { console.error(`[Micra] bus error [${event}]:`, e) }\n })\n}\n", "/**\n * src/core/reactive.ts \u2014 Reactive state proxy and batch scheduler.\n *\n * Responsibilities:\n * - Wrap a plain state object in a Proxy that notifies on writes\n * - Batch multiple synchronous mutations into a single microtask render\n *\n * LLM NOTE: Both functions are PURE constructors \u2014 they have no side effects\n * beyond setting up a Proxy / Promise chain. No DOM access here.\n */\n\nimport type { StateRecord } from '../types'\n\n/**\n * Wrap `obj` in a shallow Proxy. Any property write calls `schedule()`.\n * Arrays: replace, don't mutate \u2014 `state.items = [...state.items, x]`.\n *\n * @example\n * const raw = { count: 0 }\n * const state = createReactiveState(raw, render)\n * state.count = 5 // triggers render() in next microtask\n */\nexport function createReactiveState<S extends StateRecord>(obj: S, schedule: () => void): S {\n return new Proxy(obj, {\n set(target, key: string, value: unknown) {\n // Cast through StateRecord \u2014 TypeScript cannot write through a generic index\n ;(target as StateRecord)[key] = value\n schedule()\n return true\n },\n })\n}\n\n/**\n * Return a debounce function that defers `render` to the next microtask.\n * Multiple calls within the same tick collapse to a single render.\n *\n * @example\n * const schedule = createScheduler(render)\n * schedule() // defers render\n * schedule() // no-op \u2014 already pending\n */\nexport function createScheduler(render: () => void): () => void {\n let pending = false\n return function schedule() {\n if (pending) return\n pending = true\n Promise.resolve().then(() => { pending = false; render() })\n }\n}\n", "/**\n * src/dom/query.ts \u2014 DOM query helpers.\n *\n * LLM NOTE: These are utility functions with no side effects.\n * queryOwn is the critical function that prevents a parent component from\n * accidentally processing directives belonging to a nested child component.\n */\n\n/**\n * querySelectorAll wrapper \u2014 returns a typed array.\n */\nexport function queryAll(root: ParentNode, sel: string): Element[] {\n return Array.from(root.querySelectorAll(sel))\n}\n\n/**\n * Like querySelectorAll, but EXCLUDES elements that live inside a nested\n * `[data-component]` subtree.\n *\n * This is what prevents a parent component's render() from clobbering\n * the DOM managed by a child component.\n *\n * LLM NOTE: The walk goes up parentElement until it hits `root` or null.\n * If any ancestor (between el and root) has data-component, the element is\n * owned by that nested component, not by root's component \u2014 so we skip it.\n */\nexport function queryOwn(root: Element, attr: string): Element[] {\n return filterOwn(root, queryAll(root, `[${attr}]`))\n}\n\n/**\n * Like queryOwn but accepts an arbitrary CSS selector. Used by bindAtEvents\n * which scans `*` for `@`-prefixed attribute names (no attribute selector exists\n * for those).\n */\nexport function queryOwnAll(root: Element, sel: string): Element[] {\n return filterOwn(root, queryAll(root, sel))\n}\n\n/** @internal Shared subtree-ownership filter. */\nfunction filterOwn(root: Element, els: Element[]): Element[] {\n return els.filter(el => {\n let node: Element | null = el.parentElement\n while (node && node !== root) {\n if (node.hasAttribute('data-component')) return false\n node = node.parentElement\n }\n return true\n })\n}\n", "/**\n * src/dom/directives.ts \u2014 Apply DOM directives to a component subtree.\n *\n * Responsibilities:\n * - data-text, data-html, data-if, data-show, data-bind, data-model\n * - data-class (additive class toggling)\n * - Directive result cache (built once per element, reused on re-renders)\n *\n * LLM NOTE: applyDirectives() is called on every render. The directive cache\n * (DirectiveCache on el.__micraCache) avoids repeated querySelectorAll on\n * re-renders \u2014 cache is built lazily on the first call for each root element.\n *\n * Important: this module does NOT handle data-each \u2014 see dom/each.ts.\n */\n\nimport type {\n CachedBinding,\n CachedIfBinding,\n CachedPairBinding,\n DirectiveCache,\n InternalInstance,\n MicraElement,\n MicraTemplate,\n StateRecord,\n} from '../types'\nimport { evalExpr, warn } from '../utils/expr'\nimport { queryOwn, queryAll } from './query'\n\n// \u2500\u2500 Directive appliers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Each function is PURE relative to state \u2014 reads state, writes DOM.\n\nfunction applyText(el: Element, expr: string, state: StateRecord): void {\n const text = String(evalExpr(expr, state) ?? '')\n if (el.textContent !== text) el.textContent = text\n}\n\n/**\n * data-html \u2014 writes the expression value as innerHTML.\n *\n * \u26A0\uFE0F XSS WARNING: the value is rendered as raw HTML. Never bind untrusted\n * input here \u2014 use `data-text` (textContent) instead. See docs/directives.md\n * for the full security model.\n */\nfunction applyHtml(el: Element, expr: string, state: StateRecord): void {\n el.innerHTML = String(evalExpr(expr, state) ?? '')\n}\n\n/**\n * data-if \u2014 true mount/unmount. When the expression is falsy, the element is\n * detached from the DOM and a Comment placeholder takes its slot. When truthy,\n * the element is re-inserted where the placeholder is.\n *\n * Side effect: when an element is detached, its `data-ref` is gone from\n * `this.refs` and its `data-model` listener still exists on the (detached)\n * node \u2014 listeners survive detach.\n *\n * Use `data-show` when you want the cheap display:none toggle instead.\n */\nfunction applyIf(binding: CachedIfBinding, state: StateRecord): void {\n const el = binding.el as HTMLElement\n const truthy = !!evalExpr(binding.expr, state)\n if (truthy) {\n // If a placeholder is currently in the DOM in the element's slot, swap back.\n const ph = binding.placeholder\n if (ph && ph.parentNode) ph.parentNode.replaceChild(el, ph)\n } else {\n // Only detach if currently attached somewhere. Standalone elements\n // (no parent \u2014 common in unit tests) are a no-op.\n const parent = el.parentNode\n if (parent) {\n if (!binding.placeholder) binding.placeholder = document.createComment('if')\n parent.replaceChild(binding.placeholder, el)\n }\n }\n}\n\n/**\n * data-show \u2014 visibility toggle via `style.display`. Element stays in the DOM.\n */\nfunction applyShow(el: Element, expr: string, state: StateRecord): void {\n (el as HTMLElement).style.display = evalExpr(expr, state) ? '' : 'none'\n}\n\nfunction applyBind(\n el: Element,\n pairs: ReadonlyArray<readonly [string, string]>,\n state: StateRecord,\n): void {\n for (const [attr, valExpr] of pairs) {\n const val = evalExpr(valExpr, state)\n\n if (attr === 'class') {\n (el as HTMLElement).className = String(val ?? '')\n } else if (attr === 'value') {\n if (document.activeElement !== el)\n (el as HTMLInputElement).value = String(val ?? '')\n } else if (attr === 'style') {\n if (typeof val === 'object' && val !== null) {\n Object.assign((el as HTMLElement).style, val)\n } else {\n el.setAttribute('style', String(val ?? ''))\n }\n } else if (typeof val === 'boolean') {\n val ? el.setAttribute(attr, '') : el.removeAttribute(attr)\n } else {\n val == null ? el.removeAttribute(attr) : el.setAttribute(attr, String(val))\n }\n }\n}\n\n/**\n * data-class=\"active:isActive, disabled:count === 0\"\n * Parses comma-separated `className:expression` pairs and toggles classes additively.\n * Unlike data-bind=\"class:expr\" this does NOT replace the full className.\n *\n * Syntax mirrors data-bind \u2014 split by comma, then by first colon.\n *\n * @example\n * <div data-class=\"active:tab === 'home', hidden:!loaded\">\n */\nfunction applyClass(\n el: Element,\n pairs: ReadonlyArray<readonly [string, string]>,\n state: StateRecord,\n): void {\n for (const [cls, valExpr] of pairs) {\n el.classList.toggle(cls, Boolean(evalExpr(valExpr, state)))\n }\n}\n\n/** @internal Parse a comma+colon spec like `href:url, disabled:loading` once. */\nfunction parsePairs(expr: string): Array<readonly [string, string]> {\n const out: Array<readonly [string, string]> = []\n for (const part of expr.split(',')) {\n const colonIdx = part.indexOf(':')\n if (colonIdx === -1) continue\n const left = part.slice(0, colonIdx).trim()\n const right = part.slice(colonIdx + 1).trim()\n if (!left) continue\n out.push([left, right])\n }\n return out\n}\n\nfunction applyModel(\n el: Element,\n key: string,\n rawState: StateRecord,\n): void {\n const html = el as HTMLInputElement\n const stateVal = rawState[key]\n const desired = stateVal == null ? '' : String(stateVal)\n // Only write when out of sync. This is a no-op during live typing (the input\n // event already drove state to match el.value) but still propagates\n // programmatic resets such as `this.state.q = ''` on focused inputs.\n if (html.value !== desired) html.value = desired\n // listener is attached separately in events.ts \u2014 this only syncs the value\n}\n\n// \u2500\u2500 Directive cache \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** @internal Collect all directive bindings for a root element. Built once. */\nfunction buildCache(root: Element): DirectiveCache {\n const pick = (attr: string): CachedBinding[] => {\n const els = queryOwn(root, attr)\n // Include root itself\n if ((root as HTMLElement).hasAttribute?.(attr)) els.unshift(root)\n return els\n .filter(el => !el.closest('template'))\n .map(el => ({ el, expr: el.getAttribute(attr)! }))\n }\n const pickPairs = (attr: string): CachedPairBinding[] =>\n pick(attr).map(b => ({ ...b, pairs: parsePairs(b.expr) }))\n return {\n text: pick('data-text'),\n html: pick('data-html'),\n if: pick('data-if') as CachedIfBinding[],\n show: pick('data-show'),\n bind: pickPairs('data-bind'),\n model: pick('data-model'),\n class: pickPairs('data-class'),\n }\n}\n\n// \u2500\u2500 Main entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Apply all non-each directives to a component subtree.\n *\n * For regular Elements: directive bindings are cached in `el.__micraCache`\n * after the first call \u2014 subsequent re-renders skip querySelectorAll entirely.\n *\n * For DocumentFragments (no-key each clones): always re-scan because these\n * fragments are new clones on every render.\n *\n * @param root - Component root Element or DocumentFragment (no-key each clone)\n * @param state - Expression state (may include item/index for each rows)\n * @param rawState - Raw (non-proxy) state for model sync\n * @param instance - Component instance (unused here, kept for future hooks)\n */\nexport function applyDirectives<S extends StateRecord>(\n root: Element | DocumentFragment,\n state: StateRecord,\n rawState: StateRecord,\n _instance: InternalInstance<S>,\n): void {\n // DocumentFragments are temporary clones \u2014 always scan, never cache\n if (root.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {\n applyFromList(buildFragmentList(root as DocumentFragment), state, rawState)\n return\n }\n\n const el = root as MicraElement\n if (!el.__micraCache) el.__micraCache = buildCache(el)\n applyFromList(el.__micraCache, state, rawState)\n}\n\n/** @internal Apply a pre-built cache / binding list to current state. */\nfunction applyFromList(\n cache: DirectiveCache,\n state: StateRecord,\n rawState: StateRecord,\n): void {\n // data-if runs first so subsequent directives don't write into a tree that's\n // about to be detached this tick.\n cache.if.forEach(b => applyIf(b, state))\n cache.text.forEach(b => applyText(b.el, b.expr, state))\n cache.html.forEach(b => applyHtml(b.el, b.expr, state))\n cache.show.forEach(b => applyShow(b.el, b.expr, state))\n cache.bind.forEach(b => applyBind(b.el, b.pairs, state))\n cache.model.forEach(b => applyModel(b.el, b.expr.trim(), rawState))\n cache.class.forEach(b => applyClass(b.el, b.pairs, state))\n}\n\n/** @internal Scan a DocumentFragment (no-key each clone) \u2014 returns a DirectiveCache. */\nfunction buildFragmentList(frag: DocumentFragment): DirectiveCache {\n const pick = (attr: string): CachedBinding[] =>\n queryAll(frag, `[${attr}]`)\n .filter(el => !el.closest('template'))\n .map(el => ({ el, expr: el.getAttribute(attr)! }))\n const pickPairs = (attr: string): CachedPairBinding[] =>\n pick(attr).map(b => ({ ...b, pairs: parsePairs(b.expr) }))\n return {\n text: pick('data-text'),\n html: pick('data-html'),\n if: pick('data-if') as CachedIfBinding[],\n show: pick('data-show'),\n bind: pickPairs('data-bind'),\n model: pick('data-model'),\n class: pickPairs('data-class'),\n }\n}\n\n// \u2500\u2500 Dev warning helper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Validate directive usage and emit dev warnings.\n * Called once after the initial render of a component.\n *\n * @internal\n */\nexport function validateDirectives(root: Element): void {\n queryOwn(root, 'data-each').forEach(el => {\n const tmpl = el as MicraTemplate\n if (!el.hasAttribute('data-key') && !tmpl.__micraNoKeyWarned) {\n tmpl.__micraNoKeyWarned = true\n warn(`data-each=\"${el.getAttribute('data-each')}\" has no data-key \u2014 keyed diff disabled. Add data-key=\"id\" for better performance.`)\n }\n })\n\n // data-bind=\"class:...\" replaces className wholesale, which fights with\n // data-class on the same element. Warn so the developer picks one.\n const bindEls = queryOwn(root, 'data-bind')\n if ((root as HTMLElement).hasAttribute?.('data-bind') && !bindEls.includes(root)) bindEls.unshift(root)\n for (const el of bindEls) {\n const spec = el.getAttribute('data-bind') ?? ''\n const hasClassBind = spec.split(',').some(p => p.trim().split(':')[0]?.trim() === 'class')\n if (hasClassBind && el.hasAttribute('data-class')) {\n warn(`element has both data-bind=\"class:...\" and data-class \u2014 they fight on every render. Use one.`)\n }\n }\n}\n\n// Re-export warn for use in other modules\nexport { warn }\n", "/**\n * src/dom/events.ts \u2014 DOM event binding.\n *\n * Responsibilities:\n * - Bind `data-on=\"event:method\"` listeners (once per element)\n * - Bind `@event=\"method\"` shorthand (once per element)\n * - Bind `data-model` two-way input listeners (once per element)\n *\n * LLM NOTE: Every listener attached here is also recorded in\n * instance.__micraListeners so destroy() can remove it cleanly.\n * Re-render skips already-bound elements via per-element __micra* flags.\n */\n\nimport type { InternalInstance, MicraElement, StateRecord } from '../types'\nimport { warn } from '../utils/expr'\nimport { queryOwn, queryOwnAll, queryAll } from './query'\n\n/** @internal Attach a DOM listener and track it on the instance for destroy(). */\nfunction track<S extends StateRecord>(\n instance: InternalInstance<S>,\n el: Element,\n type: string,\n fn: EventListener,\n): void {\n el.addEventListener(type, fn)\n ;(instance.__micraListeners ??= []).push({ el, type, fn })\n}\n\n// \u2500\u2500 data-on \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Bind `data-on=\"event:method[,event2:method2]\"` listeners.\n * Listeners are bound once \u2014 re-render calls are no-ops for already-bound elements.\n *\n * Supports modifiers: `click.prevent`, `click.stop`, `click.self`.\n *\n * @example\n * <button data-on=\"click:save\">Save</button>\n * <form data-on=\"submit.prevent:handleSubmit\">\n */\nexport function bindDataOn<S extends StateRecord>(\n root: Element,\n instance: InternalInstance<S>,\n): void {\n const isFragment = root.nodeType === 11\n const els = isFragment\n ? queryAll(root as unknown as ParentNode, '[data-on]')\n : queryOwn(root, 'data-on')\n\n // Include root itself if it carries data-on (e.g., the keyed item IS the button)\n if (!isFragment && (root as HTMLElement).hasAttribute?.('data-on') && !els.includes(root))\n els.unshift(root)\n\n for (const el of els) {\n const mEl = el as MicraElement\n if (mEl.__micraEvents) continue\n mEl.__micraEvents = true\n\n const spec = mEl.dataset['on'] ?? ''\n for (const part of spec.split(',')) {\n const [evSpec, method] = part.trim().split(':') as [string, string]\n if (!evSpec || !method) continue\n\n const [evName, ...mods] = evSpec.split('.')\n\n track(instance, el, evName!, (e: Event) => {\n if (mods.includes('prevent')) e.preventDefault()\n if (mods.includes('stop')) e.stopPropagation()\n if (mods.includes('self') && e.target !== el) return\n\n const fn = instance[method.trim()]\n if (typeof fn === 'function') (fn as (e: Event) => void).call(instance, e)\n else warn(`method \"${method.trim()}\" not found`)\n })\n }\n }\n}\n\n// \u2500\u2500 @event shorthand \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Bind `@event=\"method\"` shorthand attributes (Stimulus-style).\n * Bound once per element via `__micraAtBound` \u2014 re-renders are no-ops.\n * Supports the same modifiers as data-on: `@click.prevent=\"submit\"`.\n *\n * @example\n * <button @click=\"increment\">+</button>\n * <form @submit.prevent=\"handleSubmit\">\n */\nexport function bindAtEvents<S extends StateRecord>(\n root: Element,\n instance: InternalInstance<S>,\n): void {\n const isFragment = root.nodeType === 11\n const all = isFragment\n ? queryAll(root as unknown as ParentNode, '*')\n : queryOwnAll(root, '*')\n\n // Include root itself for the regular-element case\n if (!isFragment && !all.includes(root)) all.unshift(root)\n\n for (const el of all) {\n const mEl = el as MicraElement\n if (mEl.__micraAtBound) continue\n\n let bound = false\n for (const attr of Array.from(el.attributes)) {\n if (!attr.name.startsWith('@')) continue\n const [evSpec, ...rest] = attr.name.slice(1).split('.')\n const method = attr.value.trim()\n\n track(instance, el, evSpec!, (e: Event) => {\n if (rest.includes('prevent')) e.preventDefault()\n if (rest.includes('stop')) e.stopPropagation()\n if (rest.includes('self') && e.target !== el) return\n\n const fn = instance[method]\n if (typeof fn === 'function') (fn as (e: Event) => void).call(instance, e)\n else warn(`method \"${method}\" not found`)\n })\n bound = true\n }\n if (bound) mEl.__micraAtBound = true\n }\n}\n\n// \u2500\u2500 data-model \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Two-way binding: `data-model=\"key\"` wires <input>/<select>/<textarea>\n * to `state[key]`. Binding is attached once per element.\n *\n * Numeric inputs (`type=\"number\"` / `type=\"range\"`) write numbers, not strings.\n * Checkbox inputs write booleans. Everything else writes strings.\n *\n * @example\n * <input data-model=\"search\"> // updates state.search on every keystroke\n * <select data-model=\"sortBy\"> // updates state.sortBy on change\n */\nexport function bindModels<S extends StateRecord>(\n root: Element,\n instance: InternalInstance<S>,\n): void {\n const isFragment = root.nodeType === 11\n const els = isFragment\n ? queryAll(root as unknown as ParentNode, '[data-model]')\n : queryOwn(root, 'data-model')\n\n for (const el of els) {\n const mEl = el as MicraElement\n if (mEl.__micraModel) continue\n mEl.__micraModel = true\n\n const key = (el as HTMLInputElement).dataset['model'] ?? ''\n const tag = el.tagName\n const inputEl = el as HTMLInputElement\n const inputType = inputEl.type\n\n const update = () => {\n let val: unknown\n if (tag === 'INPUT' && inputType === 'checkbox') {\n val = inputEl.checked\n } else if (tag === 'INPUT' && (inputType === 'number' || inputType === 'range')) {\n // Empty string \u2192 NaN; preserve raw empty as null so state stays \"unfilled\"\n val = inputEl.value === '' ? null : inputEl.valueAsNumber\n } else {\n val = inputEl.value\n }\n ;(instance.state as StateRecord)[key] = val\n }\n\n const evType = tag === 'SELECT' || inputType === 'radio' ? 'change' : 'input'\n track(instance, el, evType, update)\n }\n}\n", "/**\n * src/dom/each.ts \u2014 Keyed and non-keyed list rendering (data-each).\n *\n * Responsibilities:\n * - Process `<template data-each=\"items\" data-key=\"id\">` elements\n * - Keyed diff: reuse/reorder DOM nodes by key \u2014 O(n) with a Map\n * - Non-keyed fallback: full replace (no key \u2192 warn in dev, full re-render)\n * - Apply directives to each row with a scoped itemState\n *\n * LLM NOTE: renderList() is called on every render cycle AFTER applyDirectives().\n * Only <template> elements with data-each are processed.\n * Keyed mode (data-key present) mutates the DOM in-place \u2014 nodes are\n * created once and reused. Non-keyed mode removes all nodes and re-clones.\n */\n\nimport type { InternalInstance, MicraElement, MicraTemplate, StateRecord } from '../types'\nimport { evalExpr, warn } from '../utils/expr'\nimport { applyDirectives } from './directives'\nimport { bindDataOn, bindAtEvents } from './events'\nimport { queryOwn, queryAll } from './query'\n\n/**\n * Process all `<template data-each>` elements owned by `root`.\n * Scoped itemState makes `item`, `index`, `$index` available in row expressions.\n *\n * @param root - Component root Element\n * @param state - Expression state (proxy merging rawState + instance)\n * @param rawState - Raw (non-proxy) state \u2014 used for model binding\n * @param instance - Component instance (for event binding)\n */\nexport function renderList<S extends StateRecord>(\n root: Element,\n state: StateRecord,\n rawState: StateRecord,\n instance: InternalInstance<S>,\n): void {\n queryOwn(root, 'data-each').forEach(tmplEl => {\n if (tmplEl.tagName !== 'TEMPLATE') return\n const tmpl = tmplEl as MicraTemplate\n\n const itemsExpr = tmpl.getAttribute('data-each')!\n const keyAttr = tmpl.getAttribute('data-key') ?? null\n const items = evalExpr(itemsExpr, state)\n\n // Ensure marker comment + internal state are initialized\n if (!tmpl.__micraMarker) {\n const m = document.createComment(`each:${itemsExpr}`)\n tmpl.after(m)\n tmpl.__micraMarker = m\n tmpl.__micraNodes = new Map()\n tmpl.__micraList = []\n }\n\n const marker = tmpl.__micraMarker\n const keyMap = tmpl.__micraNodes\n const parent = marker.parentNode\n // The template (and its marker) is currently detached \u2014 likely a data-if\n // ancestor unmounted this subtree. Nothing to do until it returns.\n if (!parent) return\n\n // Empty / non-array: clear all rendered rows\n if (!Array.isArray(items)) {\n tmpl.__micraList.forEach(n => n.remove())\n tmpl.__micraList = []\n keyMap.clear()\n return\n }\n\n if (keyAttr) {\n renderKeyed(tmpl, items as StateRecord[], keyAttr, marker, keyMap, parent, state, rawState, instance)\n } else {\n renderNoKey(tmpl, items as StateRecord[], marker, parent, state, rawState, instance)\n }\n })\n}\n\n// \u2500\u2500 Keyed diff \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction renderKeyed<S extends StateRecord>(\n tmpl: MicraTemplate,\n items: StateRecord[],\n keyAttr: string,\n marker: Comment,\n keyMap: Map<unknown, MicraElement>,\n parent: Node,\n state: StateRecord,\n rawState: StateRecord,\n instance: InternalInstance<S>,\n): void {\n const nextKeys = new Set<unknown>()\n const nextNodes: MicraElement[] = []\n let warnedNullKey = false\n let warnedDupKey = false\n\n for (const [index, item] of items.entries()) {\n const key = item[keyAttr]\n if (key == null && !warnedNullKey) {\n warn(`data-key=\"${keyAttr}\" is null/undefined on item at index ${index}`)\n warnedNullKey = true\n }\n if (nextKeys.has(key) && !warnedDupKey) {\n warn(`data-key=\"${keyAttr}\" has duplicate value ${JSON.stringify(key)} \u2014 rows will collide`)\n warnedDupKey = true\n }\n nextKeys.add(key)\n\n let node = keyMap.get(key) as MicraElement | undefined\n\n if (!node) {\n // Clone template and wrap multi-root fragments in a display:contents element\n const frag = tmpl.content.cloneNode(true) as DocumentFragment\n if (frag.childNodes.length === 1) {\n node = frag.firstElementChild as MicraElement\n } else {\n node = document.createElement('micra-each-item') as MicraElement\n node.style.display = 'contents'\n node.append(frag)\n }\n node.__micraKey = key\n keyMap.set(key, node)\n // Bind data-on and @event handlers on the freshly created node (once)\n bindDataOn(node, instance)\n bindAtEvents(node, instance)\n }\n\n const itemState = Object.assign(\n Object.create(state) as StateRecord,\n { item, index, $index: index },\n )\n applyDirectives(node, itemState, rawState, instance)\n nextNodes.push(node)\n }\n\n // Remove stale nodes\n for (const [key, node] of keyMap) {\n if (!nextKeys.has(key)) { node.remove(); keyMap.delete(key) }\n }\n\n // Insert / reorder nodes after marker (insertBefore is no-op if already in place)\n let cursor: Node = marker\n for (const node of nextNodes) {\n if (cursor.nextSibling !== node) parent.insertBefore(node, cursor.nextSibling)\n cursor = node\n }\n\n tmpl.__micraList = nextNodes\n}\n\n// \u2500\u2500 Non-keyed (full re-render) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction renderNoKey<S extends StateRecord>(\n tmpl: MicraTemplate,\n items: StateRecord[],\n marker: Comment,\n parent: Node,\n state: StateRecord,\n rawState: StateRecord,\n instance: InternalInstance<S>,\n): void {\n tmpl.__micraList.forEach(n => n.remove())\n tmpl.__micraList = []\n\n const frag = document.createDocumentFragment()\n for (const [index, item] of items.entries()) {\n const clone = tmpl.content.cloneNode(true) as DocumentFragment\n const itemState = Object.assign(\n Object.create(state) as StateRecord,\n { item, index, $index: index },\n )\n applyDirectives(clone, itemState, rawState, instance)\n bindDataOn(clone as unknown as Element, instance)\n bindAtEvents(clone as unknown as Element, instance)\n\n const nodes = Array.from(clone.childNodes) as MicraElement[]\n nodes.forEach(n => { n.__micraEach = true; frag.append(n) })\n tmpl.__micraList.push(...nodes)\n }\n parent.insertBefore(frag, marker.nextSibling)\n}\n", "/**\n * src/dom/refs.ts \u2014 data-ref collection.\n *\n * Responsibilities:\n * - After each render, scan for `[data-ref]` elements (owned by this component)\n * - Populate `instance.refs` so methods can do `this.refs.chart` etc.\n *\n * LLM NOTE: This module is PURE relative to state \u2014 it only reads DOM attributes\n * and writes to instance.refs. It does NOT trigger renders.\n */\n\nimport type { InternalInstance, MicraElement, StateRecord } from '../types'\nimport { queryOwn } from './query'\n\n/**\n * Collect all `[data-ref=\"name\"]` elements owned by this component root into\n * `instance.refs`.\n *\n * Called once after the initial render and again on every re-render (refs may\n * point to newly created elements after an each-list update).\n *\n * @example\n * // HTML: <canvas data-ref=\"chart\">\n * // JS: this.refs.chart \u2192 HTMLCanvasElement\n */\nexport function collectRefs<S extends StateRecord>(\n root: Element,\n instance: InternalInstance<S>,\n): void {\n instance.refs = {}\n for (const el of queryOwn(root, 'data-ref')) {\n const name = (el as MicraElement).dataset['ref']\n if (name) instance.refs[name] = el as HTMLElement\n }\n}\n", "/**\n * src/core/mount.ts \u2014 Mount a component definition onto a DOM element.\n *\n * Responsibilities:\n * - Create and initialize an InternalInstance\n * - Set up reactive state + batch scheduler\n * - Wire render(), destroy(), prop(), fetch(), on(), emit()\n * - Run initial render + call onCreate() in a microtask\n *\n * LLM NOTE: This is the core of the Micra runtime.\n * mount() is called by both the public Micra.mount() API and by start()\n * (which scans the DOM for [data-component] elements).\n */\n\nimport type {\n ComponentDefinition,\n ComponentInstance,\n EventHandler,\n InternalInstance,\n MicraElement,\n StateRecord,\n UnsubFn,\n} from '../types'\nimport { warn } from '../utils/expr'\nimport { micraFetch } from '../utils/fetch'\nimport { on as busOn, emit as busEmit } from '../core/bus'\nimport { createReactiveState, createScheduler } from '../core/reactive'\nimport { applyDirectives, validateDirectives } from '../dom/directives'\nimport { renderList } from '../dom/each'\nimport { bindDataOn, bindAtEvents, bindModels } from '../dom/events'\nimport { collectRefs } from '../dom/refs'\nimport { _instances } from '../core/registry'\n\n/**\n * Mount a component definition onto a DOM element.\n * Returns the component instance, or null if the root element is not found.\n *\n * Already-mounted elements return the existing instance.\n *\n * @example\n * const instance = Micra.mount('#counter', {\n * state: { count: 0 },\n * inc() { this.state.count++ },\n * })\n */\nexport function mount<S extends StateRecord>(\n selector: string | HTMLElement,\n definition: ComponentDefinition<S>,\n): ComponentInstance<S> | null {\n const root =\n typeof selector === 'string'\n ? document.querySelector<HTMLElement>(selector)\n : selector\n\n if (!root) {\n warn(`\"${selector}\" not found`)\n return null\n }\n\n // Already mounted \u2014 return existing instance without re-mounting\n if (_instances.has(root)) return _instances.get(root) as ComponentInstance<S>\n\n const rawState: StateRecord = { ...(definition.state ?? {}) }\n const instance = { $el: root, refs: {} } as InternalInstance<S>\n\n // Copy user-defined methods from definition to instance\n for (const [key, val] of Object.entries(definition as Record<string, unknown>)) {\n if (key === 'state' || key === 'onCreate' || key === 'onDestroy') continue\n if (typeof val === 'function') instance[key] = val\n }\n\n // \u2500\u2500 prop() \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Read data-* attributes from the root element with auto-cast.\n instance.prop = function <T>(name: string, defaultVal?: T): T | undefined {\n const val = root.dataset[name]\n if (val === undefined) return defaultVal\n if (val === 'true') return true as unknown as T\n if (val === 'false') return false as unknown as T\n if (val !== '' && !isNaN(Number(val))) return Number(val) as unknown as T\n return val as unknown as T\n } as ComponentInstance<S>['prop']\n\n // \u2500\u2500 fetch(), emit(), on() \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n instance.fetch = micraFetch\n instance.emit = busEmit\n\n instance.on = <T = unknown>(event: string, handler: EventHandler<T>): UnsubFn => {\n const unsub = busOn(event, handler)\n if (!instance.__micraSubs) instance.__micraSubs = []\n instance.__micraSubs.push(unsub)\n return unsub\n }\n\n // \u2500\u2500 Render \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n let isRendering = false\n const schedule = createScheduler(() => instance.render())\n instance.state = createReactiveState(rawState, schedule) as S\n\n // Expression state: proxy that falls back to instance methods so expressions\n // like `data-text=\"formatDate(item.date)\"` can call component methods.\n //\n // Instance methods are returned BOUND to the instance \u2014 directive expressions\n // call them as bare identifiers via `with()`, which would normally lose `this`.\n // Bound copies are memoized per method name so repeated reads are cheap.\n //\n // Both traps reject Object.prototype names ('constructor', 'toString', ...) \u2014\n // accessing them via a directive expression returns undefined instead of\n // leaking the prototype.\n const boundMethods = new Map<string, Function>()\n const exprState = new Proxy(rawState, {\n get(target, key: string) {\n if (Object.prototype.hasOwnProperty.call(target, key)) return target[key]\n if (Object.prototype.hasOwnProperty.call(instance, key) &&\n typeof instance[key] === 'function') {\n const cached = boundMethods.get(key)\n if (cached) return cached\n const bound = (instance[key] as Function).bind(instance)\n boundMethods.set(key, bound)\n return bound\n }\n return undefined\n },\n has(target, key: string) {\n if (typeof key !== 'string') return false\n if (Object.prototype.hasOwnProperty.call(target, key)) return true\n return Object.prototype.hasOwnProperty.call(instance, key) &&\n typeof instance[key] === 'function'\n },\n })\n\n let warnedReentry = false\n instance.render = function () {\n if (instance.__micraDestroyed) return\n if (isRendering) {\n if (!warnedReentry) {\n warn('render() re-entry detected \u2014 mutation inside a directive expression is ignored. Move state writes to a method.')\n warnedReentry = true\n }\n return\n }\n isRendering = true\n try {\n applyDirectives(root, exprState, rawState, instance)\n renderList(root, exprState, rawState, instance)\n bindDataOn(root, instance)\n bindAtEvents(root, instance)\n bindModels(root, instance)\n collectRefs(root, instance)\n } finally {\n isRendering = false\n }\n }\n\n // \u2500\u2500 Destroy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n instance.destroy = function () {\n if (instance.__micraDestroyed) return\n instance.__micraDestroyed = true\n\n // Remove every DOM listener attached by bindDataOn / bindAtEvents / bindModels.\n instance.__micraListeners?.forEach(({ el, type, fn }) => el.removeEventListener(type, fn))\n instance.__micraListeners = []\n\n // Clear per-element flags & cached directive scan so a future re-mount of the same DOM works.\n const clearFlags = (el: Element) => {\n const m = el as MicraElement\n delete m.__micraEvents\n delete m.__micraAtBound\n delete m.__micraModel\n delete m.__micraCache\n }\n clearFlags(root)\n root.querySelectorAll('*').forEach(clearFlags)\n\n instance.__micraSubs?.forEach(unsub => unsub())\n instance.__micraSubs = []\n\n if (typeof (definition as Record<string, unknown>).onDestroy === 'function')\n (definition.onDestroy as () => void).call(instance)\n _instances.delete(root)\n }\n\n // \u2500\u2500 Bootstrap \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n _instances.set(root, instance as InternalInstance)\n instance.render()\n\n // Validate directive usage and emit dev warnings\n validateDirectives(root)\n\n if (typeof (definition as Record<string, unknown>).onCreate === 'function')\n Promise.resolve().then(() =>\n (definition.onCreate as () => void | Promise<void>).call(instance),\n )\n\n return instance\n}\n", "/**\n * src/core/start.ts \u2014 Auto-mount via [data-component].\n *\n * Responsibilities:\n * - Scan the DOM (or a subtree) for [data-component] elements\n * - Mount each using the registered definition\n * - Skip already-mounted elements (safe to call multiple times)\n * - Warn clearly when a component name is not registered\n *\n * LLM NOTE: start() is SSR-friendly \u2014 calling it multiple times is safe\n * because mount() checks _instances before re-mounting.\n */\n\nimport { warn } from '../utils/expr'\nimport { _registry, _instances } from './registry'\nimport { mount } from './mount'\n\n/**\n * Scan for `[data-component]` elements and auto-mount registered definitions.\n *\n * Pass a subtree root to limit the scan (e.g., after a partial SSR update):\n * `Micra.start(document.getElementById('panel'))`\n *\n * @example\n * // Mount everything on the page (called once after DOM ready)\n * Micra.start()\n *\n * // Re-mount after injecting new HTML\n * Micra.start(document.querySelector('#dynamic-section'))\n */\nexport function start(root: Document | HTMLElement = document): void {\n root.querySelectorAll<HTMLElement>('[data-component]').forEach(el => {\n if (_instances.has(el)) return // already mounted \u2014 skip\n const name = el.getAttribute('data-component')!\n const def = _registry.get(name)\n if (!def) {\n warn(`component \"${name}\" not defined. Call Micra.define('${name}', {...}) first.`)\n return\n }\n mount(el, def)\n })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["_a", "_a"]
|
|
7
7
|
}
|
package/dist/micra.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/* Micra.js
|
|
2
|
-
"use strict";var Micra=(()=>{var D=Object.defineProperty;var re=Object.getOwnPropertyDescriptor;var oe=Object.getOwnPropertyNames;var ae=Object.prototype.hasOwnProperty;var ie=(e,t)=>{for(var o in t)D(e,o,{get:t[o],enumerable:!0})},se=(e,t,o,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of oe(t))!ae.call(e,n)&&n!==o&&D(e,n,{get:()=>t[n],enumerable:!(r=re(t,n))||r.enumerable});return e};var ce=e=>se(D({},"__esModule",{value:!0}),e);var Me={};ie(Me,{FetchError:()=>w,debug:()=>q,define:()=>F,defineComponent:()=>k,emit:()=>O,instances:()=>H,mount:()=>L,off:()=>$,on:()=>I,registry:()=>B,start:()=>te});function de(){var e,t;return(t=(e=document.querySelector('meta[name="csrf-token"]'))==null?void 0:e.getAttribute("content"))!=null?t:null}var w=class extends Error{constructor(o,r,n){super(o);this.status=r;this.response=n;this.name="FetchError"}};async function j(e,t={}){var m,f;let o=((m=t.method)!=null?m:"GET").toUpperCase(),r={Accept:"application/json",...t.headers},n=de();n&&(r["X-CSRF-Token"]=n);let a=e,d;if(o==="GET"||o==="HEAD"){let c={};for(let[i,s]of Object.entries(t))i!=="method"&&i!=="headers"&&s!=null&&(c[i]=String(s));Object.keys(c).length&&(a+=(e.includes("?")?"&":"?")+new URLSearchParams(c))}else r["Content-Type"]="application/json",d=JSON.stringify(t.body!==void 0?t.body:t);let l=await fetch(a,{method:o,headers:r,...d!==void 0?{body:d}:{}});if(!l.ok)throw new w(`[Micra] fetch: ${o} ${e} \u2192 ${l.status}`,l.status,l);return((f=l.headers.get("content-type"))!=null?f:"").includes("application/json")?l.json():l.text()}var C=new Map,E=new Map;function F(e,t){C.set(e,t)}function k(e){return e}function H(){return E}function B(){return C}function q(){var e;if(E.size===0){console.log("[Micra] No live components.");return}console.group(`[Micra] ${E.size} live component(s)`);for(let[t,o]of E){let r=(e=t.getAttribute("data-component"))!=null?e:"(unnamed)";console.group(`%c${r}`,"font-weight:bold;color:#6366f1"),console.log("$el ",t),console.log("state",{...o.state}),console.groupEnd()}console.groupEnd()}var T=new Map,U=new Set,le=/^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/,fe=new Set(["Math","JSON","Date","String","Number","Boolean","Array","Object","parseInt","parseFloat","isNaN","isFinite","NaN","Infinity","undefined"]),ue="$s",me="$safe",pe=new Proxy(Object.create(null),{has(e,t){return typeof t!="string"||t===ue||t===me?!1:!fe.has(t)},get(){}}),K=new WeakMap,ge=new Set(Object.getOwnPropertyNames(Object.prototype));function ye(e){let t=K.get(e);if(t)return t;let o=new Proxy(e,{has(r,n){return z(r,n)},get(r,n){return Reflect.get(r,n)}});return K.set(e,o),o}function z(e,t){if(typeof t!="string"||!Reflect.has(e,t))return!1;if(!ge.has(t))return!0;let o=e;for(;o&&o!==Object.prototype;){if(Object.prototype.hasOwnProperty.call(o,t))return!0;o=Object.getPrototypeOf(o)}return!1}function _(e,t){if(le.test(e)){let o=e.split(".");return z(t,o[0])?o.reduce((r,n)=>r!=null?r[n]:void 0,t):void 0}if(!T.has(e))try{T.set(e,new Function("$s","$safe",`with($safe){with($s){return (${e})}}`))}catch{g(`invalid expression "${e}"`),T.set(e,()=>{})}try{return T.get(e)(ye(t),pe)}catch(o){U.has(e)||(U.add(e),g(`runtime error in "${e}": ${o.message}`));return}}function g(e){console.warn(`[Micra] ${e}`)}var x=new Map;function I(e,t){return x.has(e)||x.set(e,new Set),x.get(e).add(t),()=>$(e,t)}function $(e,t){let o=x.get(e);o&&(o.delete(t),o.size===0&&x.delete(e))}function O(e,t){var o;(o=x.get(e))==null||o.forEach(r=>{try{r(t)}catch(n){console.error(`[Micra] bus error [${e}]:`,n)}})}function W(e,t){return new Proxy(e,{set(o,r,n){return o[r]=n,t(),!0}})}function G(e){let t=!1;return function(){t||(t=!0,Promise.resolve().then(()=>{t=!1,e()}))}}function v(e,t){return Array.from(e.querySelectorAll(t))}function S(e,t){return v(e,`[${t}]`).filter(o=>{let r=o.parentElement;for(;r&&r!==e;){if(r.hasAttribute("data-component"))return!1;r=r.parentElement}return!0})}function he(e,t,o){var n;let r=String((n=_(t,o))!=null?n:"");e.textContent!==r&&(e.textContent=r)}function Ee(e,t,o){var r;e.innerHTML=String((r=_(t,o))!=null?r:"")}function J(e,t,o){e.style.display=_(t,o)?"":"none"}function Se(e,t,o){for(let[r,n]of t){let a=_(n,o);r==="class"?e.className=String(a!=null?a:""):r==="value"?document.activeElement!==e&&(e.value=String(a!=null?a:"")):r==="style"?typeof a=="object"&&a!==null?Object.assign(e.style,a):e.setAttribute("style",String(a!=null?a:"")):typeof a=="boolean"?a?e.setAttribute(r,""):e.removeAttribute(r):a==null?e.removeAttribute(r):e.setAttribute(r,String(a))}}function be(e,t,o){for(let[r,n]of t)e.classList.toggle(r,!!_(n,o))}function V(e){let t=[];for(let o of e.split(",")){let r=o.indexOf(":");if(r===-1)continue;let n=o.slice(0,r).trim(),a=o.slice(r+1).trim();n&&t.push([n,a])}return t}function _e(e,t,o){let r=e,n=o[t],a=n==null?"":String(n);r.value!==a&&(r.value=a)}function ve(e){let t=r=>{var a;let n=S(e,r);return(a=e.hasAttribute)!=null&&a.call(e,r)&&n.unshift(e),n.filter(d=>!d.closest("template")).map(d=>({el:d,expr:d.getAttribute(r)}))},o=r=>t(r).map(n=>({...n,pairs:V(n.expr)}));return{text:t("data-text"),html:t("data-html"),if:t("data-if"),show:t("data-show"),bind:o("data-bind"),model:t("data-model"),class:o("data-class")}}function R(e,t,o,r){if(e.nodeType===Node.DOCUMENT_FRAGMENT_NODE){Z(xe(e),t,o);return}let n=e;n.__micraCache||(n.__micraCache=ve(n)),Z(n.__micraCache,t,o)}function Z(e,t,o){e.text.forEach(r=>he(r.el,r.expr,t)),e.html.forEach(r=>Ee(r.el,r.expr,t)),e.if.forEach(r=>J(r.el,r.expr,t)),e.show.forEach(r=>J(r.el,r.expr,t)),e.bind.forEach(r=>Se(r.el,r.pairs,t)),e.model.forEach(r=>_e(r.el,r.expr.trim(),o)),e.class.forEach(r=>be(r.el,r.pairs,t))}function xe(e){let t=r=>v(e,`[${r}]`).filter(n=>!n.closest("template")).map(n=>({el:n,expr:n.getAttribute(r)})),o=r=>t(r).map(n=>({...n,pairs:V(n.expr)}));return{text:t("data-text"),html:t("data-html"),if:t("data-if"),show:t("data-show"),bind:o("data-bind"),model:t("data-model"),class:o("data-class")}}function X(e){var o,r;S(e,"data-each").forEach(n=>{let a=n;!n.hasAttribute("data-key")&&!a.__micraNoKeyWarned&&(a.__micraNoKeyWarned=!0,g(`data-each="${n.getAttribute("data-each")}" has no data-key \u2014 keyed diff disabled. Add data-key="id" for better performance.`))});let t=S(e,"data-bind");(o=e.hasAttribute)!=null&&o.call(e,"data-bind")&&!t.includes(e)&&t.unshift(e);for(let n of t)((r=n.getAttribute("data-bind"))!=null?r:"").split(",").some(l=>{var u;return((u=l.trim().split(":")[0])==null?void 0:u.trim())==="class"})&&n.hasAttribute("data-class")&&g('element has both data-bind="class:..." and data-class \u2014 they fight on every render. Use one.')}function P(e,t,o,r){var n;t.addEventListener(o,r),((n=e.__micraListeners)!=null?n:e.__micraListeners=[]).push({el:t,type:o,fn:r})}function M(e,t){var n,a;let o=e.nodeType===11,r=o?v(e,"[data-on]"):S(e,"data-on");!o&&((n=e.hasAttribute)!=null&&n.call(e,"data-on"))&&!r.includes(e)&&r.unshift(e);for(let d of r){let l=d;if(l.__micraEvents)continue;l.__micraEvents=!0;let u=(a=l.dataset.on)!=null?a:"";for(let m of u.split(",")){let[f,c]=m.trim().split(":");if(!f||!c)continue;let[i,...s]=f.split(".");P(t,d,i,p=>{if(s.includes("prevent")&&p.preventDefault(),s.includes("stop")&&p.stopPropagation(),s.includes("self")&&p.target!==d)return;let y=t[c.trim()];typeof y=="function"?y.call(t,p):g(`method "${c.trim()}" not found`)})}}}function A(e,t){let o=e.nodeType===11,r=o?v(e,"*"):v(e,"*");!o&&!r.includes(e)&&r.unshift(e);for(let n of r){let a=n;if(a.__micraAtBound)continue;let d=!1;for(let l of Array.from(n.attributes)){if(!l.name.startsWith("@"))continue;let[u,...m]=l.name.slice(1).split("."),f=l.value.trim();P(t,n,u,c=>{if(m.includes("prevent")&&c.preventDefault(),m.includes("stop")&&c.stopPropagation(),m.includes("self")&&c.target!==n)return;let i=t[f];typeof i=="function"?i.call(t,c):g(`method "${f}" not found`)}),d=!0}d&&(a.__micraAtBound=!0)}}function Y(e,t){var n;let r=e.nodeType===11?v(e,"[data-model]"):S(e,"data-model");for(let a of r){let d=a;if(d.__micraModel)continue;d.__micraModel=!0;let l=(n=a.dataset.model)!=null?n:"",u=a.tagName,m=a,f=m.type;P(t,a,u==="SELECT"||f==="radio"?"change":"input",()=>{let s;u==="INPUT"&&f==="checkbox"?s=m.checked:u==="INPUT"&&(f==="number"||f==="range")?s=m.value===""?null:m.valueAsNumber:s=m.value,t.state[l]=s})}}function Q(e,t,o,r){S(e,"data-each").forEach(n=>{var i;if(n.tagName!=="TEMPLATE")return;let a=n,d=a.getAttribute("data-each"),l=(i=a.getAttribute("data-key"))!=null?i:null,u=_(d,t);if(!a.__micraMarker){let s=document.createComment(`each:${d}`);a.after(s),a.__micraMarker=s,a.__micraNodes=new Map,a.__micraList=[]}let m=a.__micraMarker,f=a.__micraNodes,c=m.parentNode;if(!Array.isArray(u)){a.__micraList.forEach(s=>s.remove()),a.__micraList=[],f.clear();return}l?we(a,u,l,m,f,c,t,o,r):Re(a,u,m,c,t,o,r)})}function we(e,t,o,r,n,a,d,l,u){let m=new Set,f=[],c=!1,i=!1;for(let[p,y]of t.entries()){let b=y[o];b==null&&!c&&(g(`data-key="${o}" is null/undefined on item at index ${p}`),c=!0),m.has(b)&&!i&&(g(`data-key="${o}" has duplicate value ${JSON.stringify(b)} \u2014 rows will collide`),i=!0),m.add(b);let h=n.get(b);if(!h){let N=e.content.cloneNode(!0);N.childNodes.length===1?h=N.firstElementChild:(h=document.createElement("micra-each-item"),h.style.display="contents",h.append(N)),h.__micraKey=b,n.set(b,h),M(h,u),A(h,u)}let ne=Object.assign(Object.create(d),{item:y,index:p,$index:p});R(h,ne,l,u),f.push(h)}for(let[p,y]of n)m.has(p)||(y.remove(),n.delete(p));let s=r;for(let p of f)s.nextSibling!==p&&a.insertBefore(p,s.nextSibling),s=p;e.__micraList=f}function Re(e,t,o,r,n,a,d){e.__micraList.forEach(u=>u.remove()),e.__micraList=[];let l=document.createDocumentFragment();for(let[u,m]of t.entries()){let f=e.content.cloneNode(!0),c=Object.assign(Object.create(n),{item:m,index:u,$index:u});R(f,c,a,d),M(f,d),A(f,d);let i=Array.from(f.childNodes);i.forEach(s=>{s.__micraEach=!0,l.append(s)}),e.__micraList.push(...i)}r.insertBefore(l,o.nextSibling)}function ee(e,t){t.refs={};for(let o of S(e,"data-ref")){let r=o.dataset.ref;r&&(t.refs[r]=o)}}function L(e,t){var f;let o=typeof e=="string"?document.querySelector(e):e;if(!o)return g(`"${e}" not found`),null;if(E.has(o))return E.get(o);let r={...(f=t.state)!=null?f:{}},n={$el:o,refs:{}};for(let[c,i]of Object.entries(t))c==="state"||c==="onCreate"||c==="onDestroy"||typeof i=="function"&&(n[c]=i);n.prop=function(c,i){let s=o.dataset[c];return s===void 0?i:s==="true"?!0:s==="false"?!1:s!==""&&!isNaN(Number(s))?Number(s):s},n.fetch=j,n.emit=O,n.on=(c,i)=>{let s=I(c,i);return n.__micraSubs||(n.__micraSubs=[]),n.__micraSubs.push(s),s};let a=!1,d=G(()=>n.render());n.state=W(r,d);let l=new Map,u=new Proxy(r,{get(c,i){if(Object.prototype.hasOwnProperty.call(c,i))return c[i];if(Object.prototype.hasOwnProperty.call(n,i)&&typeof n[i]=="function"){let s=l.get(i);if(s)return s;let p=n[i].bind(n);return l.set(i,p),p}},has(c,i){return typeof i!="string"?!1:Object.prototype.hasOwnProperty.call(c,i)?!0:Object.prototype.hasOwnProperty.call(n,i)&&typeof n[i]=="function"}}),m=!1;return n.render=function(){if(!n.__micraDestroyed){if(a){m||(g("render() re-entry detected \u2014 mutation inside a directive expression is ignored. Move state writes to a method."),m=!0);return}a=!0;try{R(o,u,r,n),Q(o,u,r,n),M(o,n),A(o,n),Y(o,n),ee(o,n)}finally{a=!1}}},n.destroy=function(){var i,s;if(n.__micraDestroyed)return;n.__micraDestroyed=!0,(i=n.__micraListeners)==null||i.forEach(({el:p,type:y,fn:b})=>p.removeEventListener(y,b)),n.__micraListeners=[];let c=p=>{let y=p;delete y.__micraEvents,delete y.__micraAtBound,delete y.__micraModel,delete y.__micraCache};c(o),o.querySelectorAll("*").forEach(c),(s=n.__micraSubs)==null||s.forEach(p=>p()),n.__micraSubs=[],typeof t.onDestroy=="function"&&t.onDestroy.call(n),E.delete(o)},E.set(o,n),n.render(),X(o),typeof t.onCreate=="function"&&Promise.resolve().then(()=>t.onCreate.call(n)),n}function te(e=document){e.querySelectorAll("[data-component]").forEach(t=>{if(E.has(t))return;let o=t.getAttribute("data-component"),r=C.get(o);if(!r){g(`component "${o}" not defined. Call Micra.define('${o}', {...}) first.`);return}L(t,r)})}return ce(Me);})();
|
|
1
|
+
/* Micra.js v2.1.0 — https://github.com/micra-js/micra — MIT */
|
|
2
|
+
"use strict";var Micra=(()=>{var D=Object.defineProperty;var oe=Object.getOwnPropertyDescriptor;var ae=Object.getOwnPropertyNames;var ie=Object.prototype.hasOwnProperty;var se=(e,t)=>{for(var r in t)D(e,r,{get:t[r],enumerable:!0})},ce=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of ae(t))!ie.call(e,n)&&n!==r&&D(e,n,{get:()=>t[n],enumerable:!(o=oe(t,n))||o.enumerable});return e};var le=e=>ce(D({},"__esModule",{value:!0}),e);var Ie={};se(Ie,{FetchError:()=>w,debug:()=>q,define:()=>F,defineComponent:()=>k,emit:()=>O,instances:()=>H,mount:()=>N,off:()=>$,on:()=>T,registry:()=>B,start:()=>ne});function de(){var e,t;return(t=(e=document.querySelector('meta[name="csrf-token"]'))==null?void 0:e.getAttribute("content"))!=null?t:null}var w=class extends Error{constructor(r,o,n){super(r);this.status=o;this.response=n;this.name="FetchError"}};async function j(e,t={}){var m,f;let r=((m=t.method)!=null?m:"GET").toUpperCase(),o={Accept:"application/json",...t.headers},n=de();n&&(o["X-CSRF-Token"]=n);let a=e,l;if(r==="GET"||r==="HEAD"){let c={};for(let[i,s]of Object.entries(t))i!=="method"&&i!=="headers"&&i!=="signal"&&s!=null&&(c[i]=String(s));Object.keys(c).length&&(a+=(e.includes("?")?"&":"?")+new URLSearchParams(c))}else t.body!==void 0&&(o["Content-Type"]="application/json",l=JSON.stringify(t.body));let d=await fetch(a,{method:r,headers:o,...t.signal!==void 0?{signal:t.signal}:{},...l!==void 0?{body:l}:{}});if(!d.ok)throw new w(`[Micra] fetch: ${r} ${e} \u2192 ${d.status}`,d.status,d);return((f=d.headers.get("content-type"))!=null?f:"").includes("application/json")?d.json():d.text()}var A=new Map,E=new Map;function F(e,t){A.set(e,t)}function k(e){return e}function H(){return E}function B(){return A}function q(){var e;if(E.size===0){console.log("[Micra] No live components.");return}console.group(`[Micra] ${E.size} live component(s)`);for(let[t,r]of E){let o=(e=t.getAttribute("data-component"))!=null?e:"(unnamed)";console.group(`%c${o}`,"font-weight:bold;color:#6366f1"),console.log("$el ",t),console.log("state",{...r.state}),console.groupEnd()}console.groupEnd()}var I=new Map,U=new Set,fe=/^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/,ue=new Set(["Math","JSON","Date","String","Number","Boolean","Array","Object","parseInt","parseFloat","isNaN","isFinite","NaN","Infinity","undefined"]),me="$s",pe="$safe",ge=new Proxy(Object.create(null),{has(e,t){return typeof t!="string"||t===me||t===pe?!1:!ue.has(t)},get(){}}),K=new WeakMap,he=new Set(Object.getOwnPropertyNames(Object.prototype));function ye(e){let t=K.get(e);if(t)return t;let r=new Proxy(e,{has(o,n){return z(o,n)},get(o,n){return Reflect.get(o,n)}});return K.set(e,r),r}function z(e,t){if(typeof t!="string"||!Reflect.has(e,t))return!1;if(!he.has(t))return!0;let r=e;for(;r&&r!==Object.prototype;){if(Object.prototype.hasOwnProperty.call(r,t))return!0;r=Object.getPrototypeOf(r)}return!1}function _(e,t){if(fe.test(e)){let r=e.split(".");return z(t,r[0])?r.reduce((o,n)=>o!=null?o[n]:void 0,t):void 0}if(!I.has(e))try{I.set(e,new Function("$s","$safe",`with($safe){with($s){return (${e})}}`))}catch{g(`invalid expression "${e}"`),I.set(e,()=>{})}try{return I.get(e)(ye(t),ge)}catch(r){U.has(e)||(U.add(e),g(`runtime error in "${e}": ${r.message}`));return}}function g(e){console.warn(`[Micra] ${e}`)}var x=new Map;function T(e,t){return x.has(e)||x.set(e,new Set),x.get(e).add(t),()=>$(e,t)}function $(e,t){let r=x.get(e);r&&(r.delete(t),r.size===0&&x.delete(e))}function O(e,t){var r;(r=x.get(e))==null||r.forEach(o=>{try{o(t)}catch(n){console.error(`[Micra] bus error [${e}]:`,n)}})}function W(e,t){return new Proxy(e,{set(r,o,n){return r[o]=n,t(),!0}})}function G(e){let t=!1;return function(){t||(t=!0,Promise.resolve().then(()=>{t=!1,e()}))}}function v(e,t){return Array.from(e.querySelectorAll(t))}function S(e,t){return Z(e,v(e,`[${t}]`))}function J(e,t){return Z(e,v(e,t))}function Z(e,t){return t.filter(r=>{let o=r.parentElement;for(;o&&o!==e;){if(o.hasAttribute("data-component"))return!1;o=o.parentElement}return!0})}function Ee(e,t,r){var n;let o=String((n=_(t,r))!=null?n:"");e.textContent!==o&&(e.textContent=o)}function Se(e,t,r){var o;e.innerHTML=String((o=_(t,r))!=null?o:"")}function be(e,t){let r=e.el;if(!!_(e.expr,t)){let n=e.placeholder;n&&n.parentNode&&n.parentNode.replaceChild(r,n)}else{let n=r.parentNode;n&&(e.placeholder||(e.placeholder=document.createComment("if")),n.replaceChild(e.placeholder,r))}}function _e(e,t,r){e.style.display=_(t,r)?"":"none"}function ve(e,t,r){for(let[o,n]of t){let a=_(n,r);o==="class"?e.className=String(a!=null?a:""):o==="value"?document.activeElement!==e&&(e.value=String(a!=null?a:"")):o==="style"?typeof a=="object"&&a!==null?Object.assign(e.style,a):e.setAttribute("style",String(a!=null?a:"")):typeof a=="boolean"?a?e.setAttribute(o,""):e.removeAttribute(o):a==null?e.removeAttribute(o):e.setAttribute(o,String(a))}}function xe(e,t,r){for(let[o,n]of t)e.classList.toggle(o,!!_(n,r))}function X(e){let t=[];for(let r of e.split(",")){let o=r.indexOf(":");if(o===-1)continue;let n=r.slice(0,o).trim(),a=r.slice(o+1).trim();n&&t.push([n,a])}return t}function we(e,t,r){let o=e,n=r[t],a=n==null?"":String(n);o.value!==a&&(o.value=a)}function Re(e){let t=o=>{var a;let n=S(e,o);return(a=e.hasAttribute)!=null&&a.call(e,o)&&n.unshift(e),n.filter(l=>!l.closest("template")).map(l=>({el:l,expr:l.getAttribute(o)}))},r=o=>t(o).map(n=>({...n,pairs:X(n.expr)}));return{text:t("data-text"),html:t("data-html"),if:t("data-if"),show:t("data-show"),bind:r("data-bind"),model:t("data-model"),class:r("data-class")}}function R(e,t,r,o){if(e.nodeType===Node.DOCUMENT_FRAGMENT_NODE){V(Me(e),t,r);return}let n=e;n.__micraCache||(n.__micraCache=Re(n)),V(n.__micraCache,t,r)}function V(e,t,r){e.if.forEach(o=>be(o,t)),e.text.forEach(o=>Ee(o.el,o.expr,t)),e.html.forEach(o=>Se(o.el,o.expr,t)),e.show.forEach(o=>_e(o.el,o.expr,t)),e.bind.forEach(o=>ve(o.el,o.pairs,t)),e.model.forEach(o=>we(o.el,o.expr.trim(),r)),e.class.forEach(o=>xe(o.el,o.pairs,t))}function Me(e){let t=o=>v(e,`[${o}]`).filter(n=>!n.closest("template")).map(n=>({el:n,expr:n.getAttribute(o)})),r=o=>t(o).map(n=>({...n,pairs:X(n.expr)}));return{text:t("data-text"),html:t("data-html"),if:t("data-if"),show:t("data-show"),bind:r("data-bind"),model:t("data-model"),class:r("data-class")}}function Y(e){var r,o;S(e,"data-each").forEach(n=>{let a=n;!n.hasAttribute("data-key")&&!a.__micraNoKeyWarned&&(a.__micraNoKeyWarned=!0,g(`data-each="${n.getAttribute("data-each")}" has no data-key \u2014 keyed diff disabled. Add data-key="id" for better performance.`))});let t=S(e,"data-bind");(r=e.hasAttribute)!=null&&r.call(e,"data-bind")&&!t.includes(e)&&t.unshift(e);for(let n of t)((o=n.getAttribute("data-bind"))!=null?o:"").split(",").some(d=>{var u;return((u=d.trim().split(":")[0])==null?void 0:u.trim())==="class"})&&n.hasAttribute("data-class")&&g('element has both data-bind="class:..." and data-class \u2014 they fight on every render. Use one.')}function P(e,t,r,o){var n;t.addEventListener(r,o),((n=e.__micraListeners)!=null?n:e.__micraListeners=[]).push({el:t,type:r,fn:o})}function M(e,t){var n,a;let r=e.nodeType===11,o=r?v(e,"[data-on]"):S(e,"data-on");!r&&((n=e.hasAttribute)!=null&&n.call(e,"data-on"))&&!o.includes(e)&&o.unshift(e);for(let l of o){let d=l;if(d.__micraEvents)continue;d.__micraEvents=!0;let u=(a=d.dataset.on)!=null?a:"";for(let m of u.split(",")){let[f,c]=m.trim().split(":");if(!f||!c)continue;let[i,...s]=f.split(".");P(t,l,i,p=>{if(s.includes("prevent")&&p.preventDefault(),s.includes("stop")&&p.stopPropagation(),s.includes("self")&&p.target!==l)return;let h=t[c.trim()];typeof h=="function"?h.call(t,p):g(`method "${c.trim()}" not found`)})}}}function C(e,t){let r=e.nodeType===11,o=r?v(e,"*"):J(e,"*");!r&&!o.includes(e)&&o.unshift(e);for(let n of o){let a=n;if(a.__micraAtBound)continue;let l=!1;for(let d of Array.from(n.attributes)){if(!d.name.startsWith("@"))continue;let[u,...m]=d.name.slice(1).split("."),f=d.value.trim();P(t,n,u,c=>{if(m.includes("prevent")&&c.preventDefault(),m.includes("stop")&&c.stopPropagation(),m.includes("self")&&c.target!==n)return;let i=t[f];typeof i=="function"?i.call(t,c):g(`method "${f}" not found`)}),l=!0}l&&(a.__micraAtBound=!0)}}function Q(e,t){var n;let o=e.nodeType===11?v(e,"[data-model]"):S(e,"data-model");for(let a of o){let l=a;if(l.__micraModel)continue;l.__micraModel=!0;let d=(n=a.dataset.model)!=null?n:"",u=a.tagName,m=a,f=m.type;P(t,a,u==="SELECT"||f==="radio"?"change":"input",()=>{let s;u==="INPUT"&&f==="checkbox"?s=m.checked:u==="INPUT"&&(f==="number"||f==="range")?s=m.value===""?null:m.valueAsNumber:s=m.value,t.state[d]=s})}}function ee(e,t,r,o){S(e,"data-each").forEach(n=>{var i;if(n.tagName!=="TEMPLATE")return;let a=n,l=a.getAttribute("data-each"),d=(i=a.getAttribute("data-key"))!=null?i:null,u=_(l,t);if(!a.__micraMarker){let s=document.createComment(`each:${l}`);a.after(s),a.__micraMarker=s,a.__micraNodes=new Map,a.__micraList=[]}let m=a.__micraMarker,f=a.__micraNodes,c=m.parentNode;if(c){if(!Array.isArray(u)){a.__micraList.forEach(s=>s.remove()),a.__micraList=[],f.clear();return}d?Ce(a,u,d,m,f,c,t,r,o):Ae(a,u,m,c,t,r,o)}})}function Ce(e,t,r,o,n,a,l,d,u){let m=new Set,f=[],c=!1,i=!1;for(let[p,h]of t.entries()){let b=h[r];b==null&&!c&&(g(`data-key="${r}" is null/undefined on item at index ${p}`),c=!0),m.has(b)&&!i&&(g(`data-key="${r}" has duplicate value ${JSON.stringify(b)} \u2014 rows will collide`),i=!0),m.add(b);let y=n.get(b);if(!y){let L=e.content.cloneNode(!0);L.childNodes.length===1?y=L.firstElementChild:(y=document.createElement("micra-each-item"),y.style.display="contents",y.append(L)),y.__micraKey=b,n.set(b,y),M(y,u),C(y,u)}let re=Object.assign(Object.create(l),{item:h,index:p,$index:p});R(y,re,d,u),f.push(y)}for(let[p,h]of n)m.has(p)||(h.remove(),n.delete(p));let s=o;for(let p of f)s.nextSibling!==p&&a.insertBefore(p,s.nextSibling),s=p;e.__micraList=f}function Ae(e,t,r,o,n,a,l){e.__micraList.forEach(u=>u.remove()),e.__micraList=[];let d=document.createDocumentFragment();for(let[u,m]of t.entries()){let f=e.content.cloneNode(!0),c=Object.assign(Object.create(n),{item:m,index:u,$index:u});R(f,c,a,l),M(f,l),C(f,l);let i=Array.from(f.childNodes);i.forEach(s=>{s.__micraEach=!0,d.append(s)}),e.__micraList.push(...i)}o.insertBefore(d,r.nextSibling)}function te(e,t){t.refs={};for(let r of S(e,"data-ref")){let o=r.dataset.ref;o&&(t.refs[o]=r)}}function N(e,t){var f;let r=typeof e=="string"?document.querySelector(e):e;if(!r)return g(`"${e}" not found`),null;if(E.has(r))return E.get(r);let o={...(f=t.state)!=null?f:{}},n={$el:r,refs:{}};for(let[c,i]of Object.entries(t))c==="state"||c==="onCreate"||c==="onDestroy"||typeof i=="function"&&(n[c]=i);n.prop=function(c,i){let s=r.dataset[c];return s===void 0?i:s==="true"?!0:s==="false"?!1:s!==""&&!isNaN(Number(s))?Number(s):s},n.fetch=j,n.emit=O,n.on=(c,i)=>{let s=T(c,i);return n.__micraSubs||(n.__micraSubs=[]),n.__micraSubs.push(s),s};let a=!1,l=G(()=>n.render());n.state=W(o,l);let d=new Map,u=new Proxy(o,{get(c,i){if(Object.prototype.hasOwnProperty.call(c,i))return c[i];if(Object.prototype.hasOwnProperty.call(n,i)&&typeof n[i]=="function"){let s=d.get(i);if(s)return s;let p=n[i].bind(n);return d.set(i,p),p}},has(c,i){return typeof i!="string"?!1:Object.prototype.hasOwnProperty.call(c,i)?!0:Object.prototype.hasOwnProperty.call(n,i)&&typeof n[i]=="function"}}),m=!1;return n.render=function(){if(!n.__micraDestroyed){if(a){m||(g("render() re-entry detected \u2014 mutation inside a directive expression is ignored. Move state writes to a method."),m=!0);return}a=!0;try{R(r,u,o,n),ee(r,u,o,n),M(r,n),C(r,n),Q(r,n),te(r,n)}finally{a=!1}}},n.destroy=function(){var i,s;if(n.__micraDestroyed)return;n.__micraDestroyed=!0,(i=n.__micraListeners)==null||i.forEach(({el:p,type:h,fn:b})=>p.removeEventListener(h,b)),n.__micraListeners=[];let c=p=>{let h=p;delete h.__micraEvents,delete h.__micraAtBound,delete h.__micraModel,delete h.__micraCache};c(r),r.querySelectorAll("*").forEach(c),(s=n.__micraSubs)==null||s.forEach(p=>p()),n.__micraSubs=[],typeof t.onDestroy=="function"&&t.onDestroy.call(n),E.delete(r)},E.set(r,n),n.render(),Y(r),typeof t.onCreate=="function"&&Promise.resolve().then(()=>t.onCreate.call(n)),n}function ne(e=document){e.querySelectorAll("[data-component]").forEach(t=>{if(E.has(t))return;let r=t.getAttribute("data-component"),o=A.get(r);if(!o){g(`component "${r}" not defined. Call Micra.define('${r}', {...}) first.`);return}N(t,o)})}return le(Ie);})();
|
package/dist/types.d.ts
CHANGED
|
@@ -120,6 +120,14 @@ export interface CachedBinding {
|
|
|
120
120
|
el: Element;
|
|
121
121
|
expr: string;
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* @internal data-if binding — like CachedBinding but also carries the
|
|
125
|
+
* placeholder Comment that takes the element's slot in the DOM while the
|
|
126
|
+
* element is detached (unmounted).
|
|
127
|
+
*/
|
|
128
|
+
export interface CachedIfBinding extends CachedBinding {
|
|
129
|
+
placeholder?: Comment;
|
|
130
|
+
}
|
|
123
131
|
/**
|
|
124
132
|
* @internal Per-element directive binding with pre-parsed pairs.
|
|
125
133
|
* Used by `data-bind` and `data-class` — both share the
|
|
@@ -140,7 +148,7 @@ export interface CachedPairBinding {
|
|
|
140
148
|
export interface DirectiveCache {
|
|
141
149
|
text: CachedBinding[];
|
|
142
150
|
html: CachedBinding[];
|
|
143
|
-
if:
|
|
151
|
+
if: CachedIfBinding[];
|
|
144
152
|
show: CachedBinding[];
|
|
145
153
|
bind: CachedPairBinding[];
|
|
146
154
|
model: CachedBinding[];
|
package/package.json
CHANGED
package/src/dom/directives.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import type {
|
|
17
17
|
CachedBinding,
|
|
18
|
+
CachedIfBinding,
|
|
18
19
|
CachedPairBinding,
|
|
19
20
|
DirectiveCache,
|
|
20
21
|
InternalInstance,
|
|
@@ -44,7 +45,39 @@ function applyHtml(el: Element, expr: string, state: StateRecord): void {
|
|
|
44
45
|
el.innerHTML = String(evalExpr(expr, state) ?? '')
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
/**
|
|
49
|
+
* data-if — true mount/unmount. When the expression is falsy, the element is
|
|
50
|
+
* detached from the DOM and a Comment placeholder takes its slot. When truthy,
|
|
51
|
+
* the element is re-inserted where the placeholder is.
|
|
52
|
+
*
|
|
53
|
+
* Side effect: when an element is detached, its `data-ref` is gone from
|
|
54
|
+
* `this.refs` and its `data-model` listener still exists on the (detached)
|
|
55
|
+
* node — listeners survive detach.
|
|
56
|
+
*
|
|
57
|
+
* Use `data-show` when you want the cheap display:none toggle instead.
|
|
58
|
+
*/
|
|
59
|
+
function applyIf(binding: CachedIfBinding, state: StateRecord): void {
|
|
60
|
+
const el = binding.el as HTMLElement
|
|
61
|
+
const truthy = !!evalExpr(binding.expr, state)
|
|
62
|
+
if (truthy) {
|
|
63
|
+
// If a placeholder is currently in the DOM in the element's slot, swap back.
|
|
64
|
+
const ph = binding.placeholder
|
|
65
|
+
if (ph && ph.parentNode) ph.parentNode.replaceChild(el, ph)
|
|
66
|
+
} else {
|
|
67
|
+
// Only detach if currently attached somewhere. Standalone elements
|
|
68
|
+
// (no parent — common in unit tests) are a no-op.
|
|
69
|
+
const parent = el.parentNode
|
|
70
|
+
if (parent) {
|
|
71
|
+
if (!binding.placeholder) binding.placeholder = document.createComment('if')
|
|
72
|
+
parent.replaceChild(binding.placeholder, el)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* data-show — visibility toggle via `style.display`. Element stays in the DOM.
|
|
79
|
+
*/
|
|
80
|
+
function applyShow(el: Element, expr: string, state: StateRecord): void {
|
|
48
81
|
(el as HTMLElement).style.display = evalExpr(expr, state) ? '' : 'none'
|
|
49
82
|
}
|
|
50
83
|
|
|
@@ -141,7 +174,7 @@ function buildCache(root: Element): DirectiveCache {
|
|
|
141
174
|
return {
|
|
142
175
|
text: pick('data-text'),
|
|
143
176
|
html: pick('data-html'),
|
|
144
|
-
if: pick('data-if'),
|
|
177
|
+
if: pick('data-if') as CachedIfBinding[],
|
|
145
178
|
show: pick('data-show'),
|
|
146
179
|
bind: pickPairs('data-bind'),
|
|
147
180
|
model: pick('data-model'),
|
|
@@ -188,10 +221,12 @@ function applyFromList(
|
|
|
188
221
|
state: StateRecord,
|
|
189
222
|
rawState: StateRecord,
|
|
190
223
|
): void {
|
|
224
|
+
// data-if runs first so subsequent directives don't write into a tree that's
|
|
225
|
+
// about to be detached this tick.
|
|
226
|
+
cache.if.forEach(b => applyIf(b, state))
|
|
191
227
|
cache.text.forEach(b => applyText(b.el, b.expr, state))
|
|
192
228
|
cache.html.forEach(b => applyHtml(b.el, b.expr, state))
|
|
193
|
-
cache.
|
|
194
|
-
cache.show.forEach(b => applyIf(b.el, b.expr, state))
|
|
229
|
+
cache.show.forEach(b => applyShow(b.el, b.expr, state))
|
|
195
230
|
cache.bind.forEach(b => applyBind(b.el, b.pairs, state))
|
|
196
231
|
cache.model.forEach(b => applyModel(b.el, b.expr.trim(), rawState))
|
|
197
232
|
cache.class.forEach(b => applyClass(b.el, b.pairs, state))
|
|
@@ -208,7 +243,7 @@ function buildFragmentList(frag: DocumentFragment): DirectiveCache {
|
|
|
208
243
|
return {
|
|
209
244
|
text: pick('data-text'),
|
|
210
245
|
html: pick('data-html'),
|
|
211
|
-
if: pick('data-if'),
|
|
246
|
+
if: pick('data-if') as CachedIfBinding[],
|
|
212
247
|
show: pick('data-show'),
|
|
213
248
|
bind: pickPairs('data-bind'),
|
|
214
249
|
model: pick('data-model'),
|
package/src/dom/each.ts
CHANGED
|
@@ -53,7 +53,10 @@ export function renderList<S extends StateRecord>(
|
|
|
53
53
|
|
|
54
54
|
const marker = tmpl.__micraMarker
|
|
55
55
|
const keyMap = tmpl.__micraNodes
|
|
56
|
-
const parent = marker.parentNode
|
|
56
|
+
const parent = marker.parentNode
|
|
57
|
+
// The template (and its marker) is currently detached — likely a data-if
|
|
58
|
+
// ancestor unmounted this subtree. Nothing to do until it returns.
|
|
59
|
+
if (!parent) return
|
|
57
60
|
|
|
58
61
|
// Empty / non-array: clear all rendered rows
|
|
59
62
|
if (!Array.isArray(items)) {
|