type EffectFn = { (): void; e: Set>; c?: EffectFn[]; d?: boolean; }; type Signal = { (): T; (next: T | ((prev: T) => T)): T; react(): T; }; type CleanupFn = () => void; const DANGEROUS_PROTOCOLS = /^(javascript|data|vbscript):/i; const DANGEROUS_ATTRIBUTES = /^on/i; const sanitizeUrl = (url: unknown): string => { const str = String(url ?? '').trim().toLowerCase(); if (DANGEROUS_PROTOCOLS.test(str)) return '#'; return str; }; const sanitizeAttribute = (name: string, value: unknown): string | null => { if (value == null) return null; const strValue = String(value); if (DANGEROUS_ATTRIBUTES.test(name)) { console.warn(`[SigPro] XSS prevention: blocked attribute "${name}"`); return null; } if (name === 'src' || name === 'href') { return sanitizeUrl(strValue); } return strValue; }; let activeEffect: EffectFn | null = null; let isScheduled = false; const queue = new Set(); const tick = (): void => { while (queue.size) { const runs = [...queue]; queue.clear(); runs.forEach(fn => fn()); } isScheduled = false; }; const schedule = (fn: EffectFn): void => { if (fn.d) return; queue.add(fn); if (!isScheduled) { queueMicrotask(tick); isScheduled = true; } }; const depend = (subs: Set): void => { if (activeEffect && !activeEffect.c) { subs.add(activeEffect); activeEffect.e.add(subs); } }; export const effect = (fn: () => any, isScope: boolean = false): CleanupFn => { let cleanup: CleanupFn | null = null; const run = () => { if (run.d) return; const prev = activeEffect; activeEffect = run; const result = fn(); if (typeof result === 'function') cleanup = result; activeEffect = prev; }; const stop = () => { if (run.d) return; run.d = true; run.e.forEach(subs => subs.delete(run)); run.e.clear(); cleanup?.(); run.c?.forEach(f => f()); }; run.e = new Set(); run.d = false; if (isScope) run.c = []; run(); activeEffect?.c?.push(stop); return stop; }; effect.react = (fn: () => T): T => { const prev = activeEffect; activeEffect = null; const result = fn(); activeEffect = prev; return result; }; export function $(initial: T, storageKey?: string): Signal; export function $(fn: () => T, storageKey?: string): Signal; export function $(initial: T | (() => T), storageKey?: string): Signal { const isComputed = typeof initial === 'function'; if (!isComputed) { let value = initial as T; const subs = new Set(); if (storageKey) { try { const saved = localStorage.getItem(storageKey); if (saved !== null) value = JSON.parse(saved); } catch { } } const signalFn = ((...args: [] | [T | ((prev: T) => T)]) => { if (args.length === 0) { return value; } const next = typeof args[0] === 'function' ? (args[0] as (prev: T) => T)(value) : args[0]; if (Object.is(value, next)) return value; value = next; if (storageKey) { try { localStorage.setItem(storageKey, JSON.stringify(value)); } catch { } } subs.forEach(fn => schedule(fn)); return value; }) as Signal; signalFn.react = () => { depend(subs); return value; }; return signalFn; } let cached: T; let dirty = true; const subs = new Set(); const fn = initial as () => T; effect(() => { const newValue = fn(); if (!Object.is(cached, newValue) || dirty) { cached = newValue; dirty = false; subs.forEach(fn => schedule(fn)); } }); const computedFn = (() => { return cached; }) as Signal; computedFn.react = () => { depend(subs); return cached; }; return computedFn; } export const scope = (fn: () => any): CleanupFn => effect(fn, true); type WatchSource = () => T; export const watch = ( source: WatchSource, callback: (newValue: T, oldValue: T) => any ): CleanupFn => { let first = true; let oldValue: T; return effect(() => { const newValue = source(); if (!first) { effect.react(() => callback(newValue, oldValue)); } else { first = false; } oldValue = newValue; }); }; const reactiveCache = new WeakMap(); export function $$(obj: T): T { if (reactiveCache.has(obj)) { return reactiveCache.get(obj) as T; } const subs: Record> = {}; const proxy = new Proxy(obj, { get(target, key: string | symbol, receiver) { const subsForKey = subs[key] ??= new Set(); depend(subsForKey); const val = Reflect.get(target, key, receiver); return (val && typeof val === 'object') ? $$(val) : val; }, set(target, key: string | symbol, val, receiver) { if (Object.is(target[key as keyof T], val)) return true; const success = Reflect.set(target, key, val, receiver); if (subs[key]) { subs[key].forEach(fn => schedule(fn)); if (!isScheduled) { queueMicrotask(tick); isScheduled = true; } } return success; } }); reactiveCache.set(obj, proxy); return proxy; } type Context = { m: CleanupFn[]; u: CleanupFn[]; p: Record; }; let context: Context | null = null; export const onMount = (fn: CleanupFn): void => { context?.m.push(fn); }; export const onUnmount = (fn: CleanupFn): void => { context?.u.push(fn); }; export const share = (key: string | symbol, value: any): void => { if (context) context.p[key] = value; }; export const use = (key: string | symbol, defaultValue?: any): any => { if (context && key in context.p) return context.p[key]; return defaultValue; }; export function createContext(defaultValue?: T): { Provider: (props: { value: T; children?: any }) => any; use: () => T; } { const key = Symbol('context'); const useContext = (): T => { // Buscar en el stack de contextos let current = context; while (current) { if (key in current.p) { return current.p[key] as T; } // Subir al contexto padre (si existiera) current = null; // Por ahora, solo contexto actual } if (defaultValue !== undefined) return defaultValue; throw new Error(`Context not found: ${String(key)}`); }; const Provider = ({ value, children }: { value: T; children?: any }) => { // Guardar contexto anterior const prevContext = context; // Crear nuevo contexto o extender el existente if (!context) { context = { m: [], u: [], p: {} }; } // Guardar valor context.p[key] = value; // Renderizar hijos const result = h('div', { style: 'display: contents' }, children); // Restaurar contexto anterior context = prevContext; return result; }; return { Provider, use: useContext }; } export function createSharedContext(key: string | symbol, initialValue: T): { set: (value: T) => void; get: () => T; } { // Inicializar si estamos en un componente if (context && !(key in context.p)) { share(key, initialValue); } return { set: (value: T) => share(key, value), get: () => use(key) as T }; } type Component

> = ( props: P, ctx: { children?: any[]; emit: (event: string, ...args: any[]) => any } ) => any; type ElementWithLifecycle = Node & { $c?: Context; $s?: CleanupFn; $l?: (done: CleanupFn) => void; }; const isFn = (v: unknown): v is Function => typeof v === 'function'; const isNode = (v: unknown): v is Node => v instanceof Node; const append = (parent: Node, child: any): void => { if (child === null) return; if (isFn(child)) { const anchor = document.createTextNode(''); parent.appendChild(anchor); let nodes: Node[] = []; effect(() => { effect(() => { const newNodes = [child()] .flat(Infinity) .map((node: any) => isFn(node) ? node() : node) .flat(Infinity) .filter((node: any) => node !== null) .map((node: any) => isNode(node) ? node : document.createTextNode(String(node))); const oldNodes = nodes.filter(node => { const keep = newNodes.includes(node); if (!keep) remove(node); return keep; }); const oldIdxs = new Map(oldNodes.map((node, i) => [node, i])); for (let i = newNodes.length - 1, p = oldNodes.length - 1; i >= 0; i--) { const node = newNodes[i]; const ref = newNodes[i + 1] || anchor; if (!oldIdxs.has(node)) { anchor.parentNode?.insertBefore(node, ref); (node as ElementWithLifecycle).$c?.m.forEach(fn => fn()); } else if (oldNodes[p] !== node) { if (newNodes[i - 1] !== oldNodes[p]) { anchor.parentNode?.insertBefore(oldNodes[p], node); oldNodes[oldIdxs.get(node)!] = oldNodes[p]; oldNodes[p] = node; p--; } anchor.parentNode?.insertBefore(node, ref); } else { p--; } } nodes = newNodes; }); }, true); } else if (isNode(child)) { parent.appendChild(child); } else { parent.appendChild(document.createTextNode(String(child))); } }; const remove = (node: Node): void => { const el = node as ElementWithLifecycle; el.$s?.(); el.$l?.(() => node.remove()); if (!el.$l) node.remove(); }; const render = (fn: Function, ...data: any[]): Node => { let node: any; const stop = effect(() => { node = fn(...data); if (isFn(node)) node = node(); }, true); if (node) node.$s = stop; return node; }; export const h = (tag: any, props?: any, ...children: any[]): any => { props = props || {}; children = children.flat(Infinity); if (isFn(tag)) { const prev = context; context = { m: [], u: [], p: { ...(prev?.p || {}) } }; let el: any; const stop = effect(() => { el = tag(props, { children, emit: (evt: string, ...args: any[]) => props[`on${evt[0].toUpperCase()}${evt.slice(1)}`]?.(...args), }); return () => el.$c.u.forEach((fn: CleanupFn) => fn()); }, true); if (isNode(el) || isFn(el)) { el.$c = context; el.$s = stop; } context = prev; return el; } if (!tag) return () => children; let el: ElementWithLifecycle; let is_svg = false; try { el = document.createElement(tag); if (el instanceof HTMLUnknownElement) { is_svg = true; el = document.createElementNS("http://www.w3.org/2000/svg", tag); } } catch { is_svg = true; el = document.createElementNS("http://www.w3.org/2000/svg", tag); } // Código MEJORADO const booleanAttributes = ["disabled", "checked", "required", "readonly", "selected", "multiple", "autofocus"]; for (const key in props) { if (key.startsWith('on')) { const eventName = key.slice(2).toLowerCase(); el.addEventListener(eventName, props[key]); } else if (key === "ref") { if (isFn(props[key])) { props[key](el); } else { props[key].current = el; } } else if (isFn(props[key])) { effect(() => { const val = props[key](); if (key === 'className') { el.setAttribute('class', String(val ?? '')); } else if (booleanAttributes.includes(key)) { (el as any)[key] = !!val; val ? el.setAttribute(key, '') : el.removeAttribute(key); } else if (key in el && !is_svg) { (el as any)[key] = val; } else { const safeVal = sanitizeAttribute(key, val); if (safeVal !== null) el.setAttribute(key, safeVal); } }); } else { const value = props[key]; if (key === 'className') { el.setAttribute('class', String(value ?? '')); } else if (booleanAttributes.includes(key)) { (el as any)[key] = !!value; value ? el.setAttribute(key, '') : el.removeAttribute(key); } else if (key in el && !is_svg) { (el as any)[key] = value; } else { const safeVal = sanitizeAttribute(key, value); if (safeVal !== null) el.setAttribute(key, safeVal); } } } children.forEach((child: any) => append(el, child)); return el; }; export const If = ( cond: (() => boolean) | boolean, renderFn: any, fallback: any = null ): (() => any) => { let cached: any; let current: boolean | null = null; return () => { const show = isFn(cond) ? cond() : cond; if (show !== current) { cached = show ? render(renderFn) : (isFn(fallback) ? render(fallback) : fallback); } current = show; return cached; }; }; export const For = ( list: (() => T[]) | T[] | { value: T[] }, key: string | ((item: T, index: number) => string | number), renderFn: (item: T, index: number) => any ): (() => any[]) => { let cache = new Map(); return () => { const next = new Map(); const items = (isFn(list) ? list() : (list as any).value || list) as T[]; const nodes = items.map((item, index) => { const idx = isFn(key) ? key(item, index) : key ? (item as any)[key] : index; let node = cache.get(idx); if (!node) { node = render(renderFn, item, index); } next.set(idx, node); return node; }); cache = next; return nodes; }; }; type TransitionClasses = [string, string, string]; type TransitionConfig = { enter?: TransitionClasses; idle?: string; leave?: TransitionClasses; }; export const Transition = ( { enter: e, idle, leave: l }: TransitionConfig, { children: [c] }: { children: any[] } ): any => { const decorate = (el: any): any => { if (!isNode(el)) return el; const addClass = (c?: string) => c && (el as HTMLElement).classList.add(...c.split(' ')); const removeClass = (c?: string) => c && (el as HTMLElement).classList.remove(...c.split(' ')); if (e) { requestAnimationFrame(() => { addClass(e[1]); requestAnimationFrame(() => { addClass(e[0]); removeClass(e[1]); addClass(e[2]); el.addEventListener('transitionend', () => { removeClass(e[2]); removeClass(e[0]); addClass(idle); }, { once: true }); }); }); } if (l) { (el as ElementWithLifecycle).$l = (done: CleanupFn) => { removeClass(idle); addClass(l[1]); requestAnimationFrame(() => { addClass(l[0]); removeClass(l[1]); addClass(l[2]); el.addEventListener('transitionend', () => { removeClass(l[2]); removeClass(l[0]); done(); }, { once: true }); }); }; } return el; }; if (!c) return null; if (isFn(c)) { return () => decorate(c()); } return decorate(c); }; type Route = { path: string; component: Component; }; type RouterInstance = { view: Node; to: (path: string) => void; back: () => void; params: Signal>; }; export const Router = (routes: Route[]): RouterInstance => { const getPath = () => window.location.hash.slice(1) || "/"; const path = $(getPath()); const params = $>({}); const matchRoute = (path: string): { component: Component; params: Record } | null => { for (const route of routes) { const routeParts = route.path.split("/").filter(Boolean); const pathParts = path.split("/").filter(Boolean); if (routeParts.length !== pathParts.length) continue; const matchedParams: Record = {}; let ok = true; for (let i = 0; i < routeParts.length; i++) { if (routeParts[i].startsWith(":")) { matchedParams[routeParts[i].slice(1)] = pathParts[i]; } else if (routeParts[i] !== pathParts[i]) { ok = false; break; } } if (ok) return { component: route.component, params: matchedParams }; } const wildcard = routes.find(r => r.path === "*"); if (wildcard) return { component: wildcard.component, params: {} }; return null; }; window.addEventListener("hashchange", () => path(getPath())); const outlet = h("div"); effect(() => { const matched = matchRoute(path()); if (!matched) return; params(matched.params); while (outlet.firstChild) outlet.removeChild(outlet.firstChild); outlet.appendChild(h(matched.component)); }); return { view: outlet, to: (p: string) => { window.location.hash = p; }, back: () => window.history.back(), params: () => params, }; }; export const mount = ( component: Component, target: string | HTMLElement, props?: Record ): CleanupFn => { const targetEl = typeof target === "string" ? document.querySelector(target) : target; if (!targetEl) throw new Error("Target element not found"); const el = h(component, props); targetEl.appendChild(el); (el as ElementWithLifecycle).$c?.m.forEach(fn => fn()); return () => remove(el); }; export default ( target: HTMLElement, root: Component, props?: Record ): CleanupFn => { const el = h(root, props); target.appendChild(el); (el as ElementWithLifecycle).$c?.m.forEach(fn => fn()); return () => remove(el); }; declare global { namespace JSX { type Element = HTMLElement | Text | DocumentFragment | string | number | boolean | null | undefined; interface IntrinsicElements { // Elementos HTML div: HTMLAttributes; span: HTMLAttributes; p: HTMLAttributes; a: AnchorHTMLAttributes; button: ButtonHTMLAttributes; input: InputHTMLAttributes; form: FormHTMLAttributes; img: ImgHTMLAttributes; ul: HTMLAttributes; ol: HTMLAttributes; li: HTMLAttributes; h1: HTMLAttributes; h2: HTMLAttributes; h3: HTMLAttributes; h4: HTMLAttributes; h5: HTMLAttributes; h6: HTMLAttributes; section: HTMLAttributes; article: HTMLAttributes; header: HTMLAttributes; footer: HTMLAttributes; nav: HTMLAttributes; main: HTMLAttributes; aside: HTMLAttributes; label: HTMLAttributes; select: SelectHTMLAttributes; option: OptionHTMLAttributes; textarea: TextareaHTMLAttributes; table: TableHTMLAttributes; tr: HTMLAttributes; td: HTMLAttributes; th: HTMLAttributes; hr: HTMLAttributes; br: HTMLAttributes; // SVG svg: SVGAttributes; path: SVGAttributes; circle: SVGAttributes; rect: SVGAttributes; line: SVGAttributes; g: SVGAttributes; } interface HTMLAttributes { id?: string; className?: string; class?: string; style?: string | Partial; children?: any; ref?: ((el: any) => void) | { current: any }; // Eventos onClick?: (event: MouseEvent) => void; onInput?: (event: Event) => void; onChange?: (event: Event) => void; onSubmit?: (event: Event) => void; onKeyDown?: (event: KeyboardEvent) => void; onKeyUp?: (event: KeyboardEvent) => void; onFocus?: (event: FocusEvent) => void; onBlur?: (event: FocusEvent) => void; onMouseEnter?: (event: MouseEvent) => void; onMouseLeave?: (event: MouseEvent) => void; // Atributos ARIA role?: string; 'aria-label'?: string; 'aria-hidden'?: boolean | 'true' | 'false'; 'aria-expanded'?: boolean | 'true' | 'false'; // Atributos comunes tabIndex?: number; title?: string; draggable?: boolean; hidden?: boolean; } interface AnchorHTMLAttributes extends HTMLAttributes { href?: string; target?: '_blank' | '_self' | '_parent' | '_top'; rel?: string; } interface ButtonHTMLAttributes extends HTMLAttributes { type?: 'button' | 'submit' | 'reset'; disabled?: boolean; } interface InputHTMLAttributes extends HTMLAttributes { type?: 'text' | 'password' | 'email' | 'number' | 'checkbox' | 'radio' | 'file' | 'date'; value?: string | number; checked?: boolean; placeholder?: string; disabled?: boolean; required?: boolean; name?: string; min?: number; max?: number; step?: number; } interface FormHTMLAttributes extends HTMLAttributes { action?: string; method?: 'get' | 'post'; } interface ImgHTMLAttributes extends HTMLAttributes { src?: string; alt?: string; width?: number | string; height?: number | string; loading?: 'lazy' | 'eager'; } interface SelectHTMLAttributes extends HTMLAttributes { value?: string | string[]; disabled?: boolean; required?: boolean; multiple?: boolean; } interface OptionHTMLAttributes extends HTMLAttributes { value?: string | number; selected?: boolean; disabled?: boolean; } interface TextareaHTMLAttributes extends HTMLAttributes { value?: string; placeholder?: string; disabled?: boolean; required?: boolean; rows?: number; cols?: number; } interface TableHTMLAttributes extends HTMLAttributes { border?: number; cellPadding?: number | string; cellSpacing?: number | string; } interface SVGAttributes { viewBox?: string; width?: number | string; height?: number | string; fill?: string; stroke?: string; strokeWidth?: number | string; xmlns?: string; children?: any; } } } export { h as jsx, h as jsxs, h as Fragment }; export type { Signal, Component, CleanupFn };