From e9ca8d397b0a99e6b0ce8f60e5f718057c3feb31 Mon Sep 17 00:00:00 2001 From: natxocc Date: Wed, 8 Apr 2026 11:08:58 +0200 Subject: [PATCH] Eliminar sigpro.ts --- sigpro.ts | 855 ------------------------------------------------------ 1 file changed, 855 deletions(-) delete mode 100644 sigpro.ts diff --git a/sigpro.ts b/sigpro.ts deleted file mode 100644 index e862da9..0000000 --- a/sigpro.ts +++ /dev/null @@ -1,855 +0,0 @@ -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 };