const isFn = (v) => typeof v === 'function'; const isNode = (v) => v instanceof Node; let isScheduled = false, activeEffect = null, context = null; const queue = new Set(), reactiveCache = new WeakMap(); const tick = () => { queue.forEach(fn => fn()); queue.clear(); isScheduled = false; } // Helper para extraer valor de signals o funciones const unwrap = (v) => (v?._isSig ? v.value : (isFn(v) ? v() : v)); export const effect = (fn, is_scope = false) => { let cleanup = null; const run = () => { stop(); const prev = activeEffect; activeEffect = run; try { cleanup = fn(); } finally { activeEffect = prev; } } const stop = () => { run.e.forEach(subs => subs.delete(run)); run.e.clear(); if (isFn(cleanup)) cleanup(); if (run.c) { run.c.forEach(s => s()); run.c.length = 0; } } run.e = new Set(); if (is_scope) run.c = []; run(); if (activeEffect?.c) activeEffect.c.push(stop); return stop; } const track = (subs) => { if (activeEffect && !activeEffect.c) { subs.add(activeEffect); activeEffect.e.add(subs); } } export const signal = (value) => { const subs = new Set(); return { _isSig: true, get value() { track(subs); return value; }, set value(v) { if (v === value) return; value = v; subs.forEach(f => queue.add(f)); if (!isScheduled) { isScheduled = true; queueMicrotask(tick); } } } } export const untrack = (fn) => { const prev = activeEffect; activeEffect = null; const res = fn(); activeEffect = prev; return res; } export const computed = (fn) => { const sig = signal(); effect(() => sig.value = fn()); return { get value() { return sig.value; } }; } export const reactive = (obj) => { if (reactiveCache.has(obj)) return reactiveCache.get(obj); const subs = {}; const proxy = new Proxy(obj, { get(t, k) { track(subs[k] ??= new Set()); const v = t[k]; return (v && typeof v === 'object') ? reactive(v) : v; }, set(t, k, v) { if (t[k] === v) return true; t[k] = v; if (subs[k]) { subs[k].forEach(f => queue.add(f)); if (!isScheduled) { isScheduled = true; queueMicrotask(tick); } } return true; } }); reactiveCache.set(obj, proxy); return proxy; } export const persist = (key, target) => { const saved = localStorage.getItem(key); if (saved !== null) { const data = JSON.parse(saved); target._isSig ? (target.value = data) : Object.assign(target, data); } effect(() => localStorage.setItem(key, JSON.stringify(target._isSig ? target.value : target))); return target; }; export const storage = (key, val) => persist(key, signal(val)); export const watch = (source, cb) => { let first = true, old; return effect(() => { const val = unwrap(source); if (!first) untrack(() => cb(val, old)); first = false; old = val; }); } export const onMount = (f) => context?.m.push(f); export const onUnmount = (f) => context?.u.push(f); export const provide = (k, v) => context && (context.p[k] = v); export const inject = (k, d) => context && (k in context.p ? context.p[k] : d); const remove = async (n) => { if (Array.isArray(n)) return Promise.all(n.map(remove)); if (n.$off) await n.$off(n); else if (n.$l) await new Promise(r => n.$l(r)); n.$s?.(); if (n.$c) n.$c.u.forEach(f => f()); n.remove(); } const render = (fn, ...d) => { let n; const s = effect(() => { n = fn(...d); if (isFn(n)) n = n(); }, true); if (n) n.$s = s; return n; } export const h = (tag, props = {}, ...children) => { children = children.flat(Infinity); if (isFn(tag)) { const prev = context; context = { m: [], u: [], p: { ...(prev?.p || {}) } }; const ctx = context; let el; const s = effect(() => { el = tag(props, { children, emit: (e, ...a) => props[`on${e[0].toUpperCase()}${e.slice(1)}`]?.(...a) }); return () => ctx.u.forEach(f => f()); }, true); const out = isNode(el) ? el : document.createTextNode(String(el)); out.$c = ctx; out.$s = s; if (props.on) out.$on = props.on; if (props.off) out.$off = props.off; context = prev; return out; } if (!tag) return children; const isSvg = /^(svg|path|circle|rect)$/.test(tag); const el = isSvg ? document.createElementNS("http://www.w3.org/2000/svg", tag) : document.createElement(tag); for (const k in props) { const v = props[k]; if (k.startsWith('on') && k !== 'on' && k !== 'off') el.addEventListener(k.slice(2).toLowerCase(), v); else if (k === "ref") isFn(v) ? v(el) : v.value = el; else if (k === "on") el.$on = v; else if (k === "off") el.$off = v; else if (isFn(v) || v?._isSig) effect(() => el[k] = unwrap(v)); else el[k] = v; } children.forEach(c => append(el, c)); return el; } const append = (p, c) => { if (c == null) return; if (isFn(c) || c?._isSig) { const anchor = document.createTextNode(''); p.appendChild(anchor); let nodes = []; effect(async () => { const raw = [unwrap(c)].flat(Infinity).filter(n => n != null); const next = raw.map(n => isNode(n) ? n : document.createTextNode(String(n))); for (const n of nodes) { if (!next.includes(n)) await remove(n); } next.forEach((n, i) => { if (!nodes.includes(n)) { p.insertBefore(n, next[i+1] || anchor); if (n.$on) n.$on(n); if (n.$c) n.$c.m.forEach(f => f()); } }); nodes = next; }, true); } else { const n = isNode(c) ? c : document.createTextNode(String(c)); p.appendChild(n); if (n.$on) n.$on(n); } } export const If = (cond, t, f = null, trans = {}) => { let cached, current; return () => { const show = !!unwrap(cond); if (show !== current) { const up = async () => { if (cached) await remove(cached); cached = show ? render(t) : (isFn(f) ? render(f) : f); if (isNode(cached)) { if (trans.on) cached.$on = trans.on; if (trans.off) cached.$off = trans.off; } current = show; }; up(); } return cached; } } export const For = (list, key, renderFn) => { let cache = new Map(); return () => { const next = new Map(); const items = unwrap(list); const res = items.map((item, i) => { const id = isFn(key) ? key(item, i) : (key ? item[id] : item); let n = cache.get(id); if (!n) n = render(renderFn, item, i); next.set(id, n); return n; }); cache.forEach(async (n, id) => { if (!next.has(id)) await remove(n); }); cache = next; return res; } } export const Router = (routes, trans = {}) => { const path = signal(window.location.hash.slice(1) || '/'); window.onhashchange = () => path.value = window.location.hash.slice(1) || '/'; return h('div', { class: 'router-view' }, () => { const p = path.value; const r = routes.find(x => x.path === p) || routes.find(x => x.path === '*'); return If(() => !!r, () => h(r.component, { path: p }), null, trans); }); }; export const Transition = ({ enter: e, idle, leave: l }, { children: [c] }) => { const decorate = (el) => { if (!isNode(el)) return el; const add = (cl) => cl && el.classList.add(...cl.split(' ')); const rem = (cl) => cl && el.classList.remove(...cl.split(' ')); el.$on = () => { if (!e) return; requestAnimationFrame(() => { add(e[1]); requestAnimationFrame(() => { add(e[0]); rem(e[1]); add(e[2]); el.addEventListener('transitionend', () => { rem(e[2]); rem(e[0]); add(idle); }, { once: true }); }); }); }; el.$off = () => { if (!l) return el.remove(); return new Promise(res => { rem(idle); add(l[1]); requestAnimationFrame(() => { add(l[0]); rem(l[1]); add(l[2]); el.addEventListener('transitionend', () => { rem(l[2]); rem(l[0]); res(); }, { once: true }); }); }); }; return el; } return isFn(c) ? () => decorate(c()) : decorate(c); } export const mount = (root, target, props = {}) => { const container = typeof target === 'string' ? document.querySelector(target) : target; if (container.firstElementChild) remove(container.firstElementChild); const el = h(root, props); container.appendChild(el); if (el.$on) el.$on(el); if (el.$c) el.$c.m.forEach(f => f()); return () => remove(el); }; export default { signal, effect, reactive, computed, watch, persist, storage, h, mount, If, For, Router, Transition, onMount, onUnmount, provide, inject };