diff --git a/packages/sigpro/sigpro2.js b/packages/sigpro/sigpro2.js index 7098767..b847fb4 100644 --- a/packages/sigpro/sigpro2.js +++ b/packages/sigpro/sigpro2.js @@ -1,8 +1,21 @@ +/** + * SigPro 2.0 - Core Engine + * @author Gemini & User + */ (() => { + /** @type {Function|null} */ let activeEffect = null; + /** + * Crea una Señal (Estado) o una Computada (Derivado). + * @template T + * @param {T|function():T} initial - Valor inicial o función de cálculo. + * @returns {{(newValue?: T|function(T):T): T}} Getter/Setter de la señal. + */ window.$ = (initial) => { + /** @type {Set} */ const subs = new Set(); + if (typeof initial === 'function') { let cached; const runner = () => { @@ -17,6 +30,7 @@ return cached; }; } + return (...args) => { if (args.length) { const next = typeof args[0] === 'function' ? args[0](initial) : args[0]; @@ -30,7 +44,12 @@ }; }; - window.$e = (fn) => { + /** + * Crea un Efecto secundario reactivo. + * @param {function():void} fn - Función a ejecutar cuando cambien sus dependencias. + * @returns {function():void} La función del efecto. + */ + window._$ = (fn) => { const effect = () => { const prev = activeEffect; activeEffect = effect; @@ -40,8 +59,18 @@ return effect; }; - const h = (tag, props = {}, children = []) => { + /** + * Constructor Universal de Elementos DOM. + * @param {string} tag - Etiqueta HTML. + * @param {Object | HTMLElement | Array | string} [props] - Propiedades o hijos. + * @param {Array | HTMLElement | string | function} [children] - Hijos del elemento. + * @returns {HTMLElement} El elemento DOM creado. + */ + window.$$ = (tag, props = {}, children = []) => { const el = document.createElement(tag); + if (typeof props !== 'object' || props instanceof Node || Array.isArray(props)) { + children = props; props = {}; + } for (let [key, val] of Object.entries(props)) { if (key.startsWith('on')) { el.addEventListener(key.toLowerCase().slice(2), val); @@ -51,27 +80,25 @@ const ev = attr === 'checked' ? 'change' : 'input'; el.addEventListener(ev, e => val(attr === 'checked' ? e.target.checked : e.target.value)); } - $e(() => { + _$(() => { const v = typeof val === 'function' ? val() : val; if (attr === 'value' || attr === 'checked') el[attr] = v; else if (typeof v === 'boolean') el.toggleAttribute(attr, v); - else el.setAttribute(attr, v); + else el.setAttribute(attr, v ?? ''); }); } else { el.setAttribute(key, val); } } + /** @param {any} c */ const append = (c) => { if (Array.isArray(c)) return c.forEach(append); if (typeof c === 'function') { const node = document.createTextNode(''); - $e(() => { + _$(() => { const res = c(); - if (res instanceof Node) { - if (node.parentNode) node.replaceWith(res); - } else { - node.textContent = res ?? ''; - } + if (res instanceof Node) { if (node.parentNode) node.replaceWith(res); } + else { node.textContent = res ?? ''; } }); return el.appendChild(node); } @@ -81,68 +108,13 @@ return el; }; - const tags = ['div', 'span', 'p', 'button', 'input', 'h1', 'h2', 'label', 'section', 'ul', 'li', 'a', 'header', 'footer', 'nav', 'main']; - tags.forEach(tag => { - window[`_${tag}`] = (props, children) => { - if (typeof props !== 'object' || props instanceof Node || Array.isArray(props)) { - return h(tag, {}, props); - } - return h(tag, props, children); - }; - }); - - window._storage = (key, initial, storage = localStorage) => { - const saved = storage.getItem(key); - const signal = $((saved !== null) ? JSON.parse(saved) : initial); - $e(() => storage.setItem(key, JSON.stringify(signal()))); - return signal; + /** + * Renderiza la aplicación en un contenedor. + * @param {HTMLElement | function():HTMLElement} node - Elemento raíz o función que lo retorna. + * @param {HTMLElement} [target] - Contenedor destino (por defecto document.body). + */ + window.$render = (node, target = document.body) => { + target.innerHTML = ''; + target.appendChild(typeof node === 'function' ? node() : node); }; - - window._router = (routes) => { - const path = $(window.location.hash.replace(/^#/, "") || "/"); - window.addEventListener("hashchange", () => path(window.location.hash.replace(/^#/, "") || "/")); - - return _div({ class: "router-container" }, [ - () => { - const current = path(); - - let params = {}; - const route = routes.find(r => { - const routeParts = r.path.split('/').filter(Boolean); - const currentParts = current.split('/').filter(Boolean); - if (routeParts.length !== currentParts.length) return false; - - return routeParts.every((part, i) => { - if (part.startsWith(':')) { - params[part.slice(1)] = currentParts[i]; - return true; - } - return part === currentParts[i]; - }); - }) || routes.find(r => r.path === "*"); - - if (!route) return _h1("404"); - return typeof route.component === 'function' - ? route.component(params) - : route.component; - } - ]); -}; - - window._render = (node, target = document.body) => { - target.innerHTML = ''; - const element = typeof node === 'function' ? node() : node; - target.appendChild(element); - return element; - }; - - window.Row = (props, children) => _div({ - ...((typeof props === 'object' && !Array.isArray(props)) ? props : {}), - style: `display:flex; flex-direction:row; gap:10px; ${props?.style || ''}` - }, (Array.isArray(props) ? props : children)); - - window.Col = (props, children) => _div({ - ...((typeof props === 'object' && !Array.isArray(props)) ? props : {}), - style: `display:flex; flex-direction:column; gap:10px; ${props?.style || ''}` - }, (Array.isArray(props) ? props : children)); })();