From 876874c2f07d24a7068430263a25e326512ed505 Mon Sep 17 00:00:00 2001 From: natxocc Date: Thu, 26 Mar 2026 14:06:49 +0100 Subject: [PATCH] 1.1.5 --- UI/sigpro-ui.js | 156 ++++------- UI/sigpro-ui.min.js | 2 +- docs/404.html | 2 +- docs/api/effect.html | 58 ---- docs/api/html.html | 14 +- docs/api/ignore.html | 52 ---- docs/api/mount.html | 29 +- docs/api/quick.html | 4 +- docs/api/router.html | 4 +- docs/api/signal.html | 6 +- docs/api/tags.html | 22 +- docs/api/view.html | 51 ---- docs/api/watch.html | 58 ++++ docs/assets/api_effect.md.jV8KzXq5.js | 33 --- docs/assets/api_effect.md.jV8KzXq5.lean.js | 1 - ...md.COPskx0H.js => api_html.md.BPbZMZR1.js} | 10 +- ...H.lean.js => api_html.md.BPbZMZR1.lean.js} | 2 +- docs/assets/api_ignore.md.CxKek-H-.js | 27 -- docs/assets/api_ignore.md.CxKek-H-.lean.js | 1 - docs/assets/api_mount.md.BiKjH18I.js | 16 ++ docs/assets/api_mount.md.BiKjH18I.lean.js | 1 + docs/assets/api_mount.md.CRwLyxt8.js | 27 -- docs/assets/api_mount.md.CRwLyxt8.lean.js | 1 - ...md.VliNqepa.js => api_tags.md.CW_zjfl9.js} | 18 +- ...a.lean.js => api_tags.md.CW_zjfl9.lean.js} | 0 docs/assets/api_view.md.Bv8Rlx9s.js | 26 -- docs/assets/api_view.md.Bv8Rlx9s.lean.js | 1 - docs/assets/api_watch.md.D7sOEzCX.js | 33 +++ docs/assets/api_watch.md.D7sOEzCX.lean.js | 1 + ....md.DmlvO98W.js => install.md.C0utklUK.js} | 4 +- ...8W.lean.js => install.md.C0utklUK.lean.js} | 0 ...md.Bzj-nQ2u.js => ui_quick.md.CsppjR8J.js} | 14 +- ...u.lean.js => ui_quick.md.CsppjR8J.lean.js} | 2 +- docs/examples.html | 4 +- docs/hashmap.json | 2 +- docs/index.html | 2 +- docs/install.html | 8 +- docs/ui/quick.html | 18 +- docs/vite/plugin.html | 4 +- package.json | 2 +- sigpro/sigpro.js | 263 ++++++++---------- sigpro/sigpro.min.js | 2 +- src/docs/.vitepress/config.js | 4 +- src/docs/api/effect.md | 90 ------ src/docs/api/html.md | 13 +- src/docs/api/ignore.md | 75 ----- src/docs/api/mount.md | 91 +++--- src/docs/api/tags.md | 36 ++- src/docs/api/view.md | 78 ------ src/docs/api/watch.md | 91 ++++++ src/docs/ui/quick.md | 43 +-- 51 files changed, 535 insertions(+), 967 deletions(-) delete mode 100644 docs/api/effect.html delete mode 100644 docs/api/ignore.html delete mode 100644 docs/api/view.html create mode 100644 docs/api/watch.html delete mode 100644 docs/assets/api_effect.md.jV8KzXq5.js delete mode 100644 docs/assets/api_effect.md.jV8KzXq5.lean.js rename docs/assets/{api_html.md.COPskx0H.js => api_html.md.BPbZMZR1.js} (56%) rename docs/assets/{api_html.md.COPskx0H.lean.js => api_html.md.BPbZMZR1.lean.js} (62%) delete mode 100644 docs/assets/api_ignore.md.CxKek-H-.js delete mode 100644 docs/assets/api_ignore.md.CxKek-H-.lean.js create mode 100644 docs/assets/api_mount.md.BiKjH18I.js create mode 100644 docs/assets/api_mount.md.BiKjH18I.lean.js delete mode 100644 docs/assets/api_mount.md.CRwLyxt8.js delete mode 100644 docs/assets/api_mount.md.CRwLyxt8.lean.js rename docs/assets/{api_tags.md.VliNqepa.js => api_tags.md.CW_zjfl9.js} (53%) rename docs/assets/{api_tags.md.VliNqepa.lean.js => api_tags.md.CW_zjfl9.lean.js} (100%) delete mode 100644 docs/assets/api_view.md.Bv8Rlx9s.js delete mode 100644 docs/assets/api_view.md.Bv8Rlx9s.lean.js create mode 100644 docs/assets/api_watch.md.D7sOEzCX.js create mode 100644 docs/assets/api_watch.md.D7sOEzCX.lean.js rename docs/assets/{install.md.DmlvO98W.js => install.md.C0utklUK.js} (96%) rename docs/assets/{install.md.DmlvO98W.lean.js => install.md.C0utklUK.lean.js} (100%) rename docs/assets/{ui_quick.md.Bzj-nQ2u.js => ui_quick.md.CsppjR8J.js} (59%) rename docs/assets/{ui_quick.md.Bzj-nQ2u.lean.js => ui_quick.md.CsppjR8J.lean.js} (52%) delete mode 100644 src/docs/api/effect.md delete mode 100644 src/docs/api/ignore.md delete mode 100644 src/docs/api/view.md create mode 100644 src/docs/api/watch.md diff --git a/UI/sigpro-ui.js b/UI/sigpro-ui.js index 7861362..11727e2 100644 --- a/UI/sigpro-ui.js +++ b/UI/sigpro-ui.js @@ -63,66 +63,6 @@ export const UI = ($, defaultLang = "es") => { const iconRRight = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABmSURBVDiN3dGxCoAgEMbxfz1dL1BTREJzmUv08trgDYcg6VCD3/YD7zvkoLmMgFEegLmmwAAecOJVvNeUWCAAt7IHjt9LThkyiRf9qC8oCom70u0BuDL+bngj/tNm/JqJePucW8wDvGYdzT0nMUkAAAAASUVORK5CYII="; - // --- UTILITY FUNCTIONS --- - - /** IF */ - ui.If = (condition, thenValue, otherwiseValue = null) => { - return () => { - const isTrue = val(condition); - const result = isTrue ? thenValue : otherwiseValue; - if (typeof result === "function" && !(result instanceof HTMLElement)) { - return result(); - } - return result; - }; - }; - - /** FOR */ - ui.For = (source, render, keyFn) => { - if (typeof keyFn !== "function") throw new Error("SigPro UI: For requires a keyFn."); - - const marker = document.createTextNode(""); - const container = $.html("div", { style: "display:contents" }, [marker]); - const cache = new Map(); - - $.effect(() => { - const items = val(source) || []; - const newKeys = new Set(); - - items.forEach((item, index) => { - const key = keyFn(item, index); - newKeys.add(key); - - let runtime = cache.get(key); - if (!runtime) { - runtime = $.view(() => render(item, index)); - cache.set(key, runtime); - } - container.insertBefore(runtime.container, marker); - }); - - cache.forEach((runtime, key) => { - if (!newKeys.has(key)) { - runtime.destroy(); - cache.delete(key); - } - }); - }); - - return container; - }; - - /** JSON */ - ui.Json = (data, space = 2) => { - return Span({ class: "font-mono whitespace-pre-wrap" }, () => { - try { - return JSON.stringify(val(data), null, space); - } catch (e) { - return "[Error: Circular or Invalid JSON]"; - } - }); - }; - /** REQ */ ui.Request = (url, payload = null, options = {}) => { const data = $(null), @@ -176,14 +116,14 @@ export const UI = ($, defaultLang = "es") => { /** RESPONSE */ ui.Response = (reqObj, renderFn) => $.html("div", { class: "res-container" }, [ - ui.If(reqObj.loading, $.html("div", { class: "flex justify-center p-4" }, $.html("span", { class: "loading loading-dots text-primary" }))), - ui.If(reqObj.error, () => + $.if(reqObj.loading, $.html("div", { class: "flex justify-center p-4" }, $.html("span", { class: "loading loading-dots text-primary" }))), + $.if(reqObj.error, () => $.html("div", { role: "alert", class: "alert alert-error" }, [ $.html("span", {}, reqObj.error()), ui.Button({ class: "btn-xs btn-ghost border-current", onclick: () => reqObj.reload() }, "Retry"), ]), ), - ui.If(reqObj.success, () => { + $.if(reqObj.success, () => { const current = reqObj.data(); return current !== null ? renderFn(current) : null; }), @@ -300,7 +240,7 @@ export const UI = ($, defaultLang = "es") => { $value: $value, onchange: (e) => $value?.(e.target.value), }, - ui.For( + $.for( () => val(options) || [], (opt) => $.html( @@ -387,7 +327,7 @@ export const UI = ($, defaultLang = "es") => { style: () => (isOpen() && list().length ? "display:block" : "display:none"), }, [ - ui.For( + $.for( list, (opt, i) => $.html("li", {}, [ @@ -481,7 +421,7 @@ export const UI = ($, defaultLang = "es") => { ...rest, }), - ui.If(isOpen, () => + $.if(isOpen, () => $.html( "div", { @@ -579,7 +519,7 @@ export const UI = ($, defaultLang = "es") => { ), ), - ui.If(isOpen, () => $.html("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) })), + $.if(isOpen, () => $.html("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) })), ]); }; @@ -622,7 +562,7 @@ export const UI = ($, defaultLang = "es") => { ], ), - ui.If(isOpen, () => + $.if(isOpen, () => $.html( "div", { @@ -653,7 +593,7 @@ export const UI = ($, defaultLang = "es") => { ), ), - ui.If(isOpen, () => + $.if(isOpen, () => $.html("div", { class: "fixed inset-0 z-[100]", onclick: () => isOpen(false), @@ -729,7 +669,7 @@ export const UI = ($, defaultLang = "es") => { const { title, buttons, $open, ...rest } = props; const close = () => $open(false); - return ui.If($open, () => + return $.if($open, () => $.html("dialog", { ...rest, class: "modal modal-open" }, [ $.html("div", { class: "modal-box" }, [ title ? $.html("h3", { class: "text-lg font-bold mb-4" }, title) : null, @@ -773,7 +713,7 @@ export const UI = ($, defaultLang = "es") => { container._cleanups.add(() => observer.disconnect()); - const stopGrid = $.effect(() => { + const stopGrid = $.watch(() => { const dark = isDark(); const agTheme = getTheme(dark); const rowData = val(data) || []; @@ -790,7 +730,7 @@ export const UI = ($, defaultLang = "es") => { }); container._cleanups.add(stopGrid); - const stopData = $.effect(() => { + const stopData = $.watch(() => { const rowData = val(data); if (gridApi && Array.isArray(rowData)) { gridApi.setGridOption("rowData", rowData); @@ -875,7 +815,7 @@ export const UI = ($, defaultLang = "es") => { role: "tablist", class: joinClass("tabs tabs-box", props.$class || props.class), }, - ui.For( + $.for( itemsSignal, (it) => $.html( @@ -914,7 +854,7 @@ export const UI = ($, defaultLang = "es") => { /** MENU */ ui.Menu = (props) => { const renderItems = (items) => - ui.For( + $.for( () => items || [], (it) => $.html("li", {}, [ @@ -977,8 +917,8 @@ export const UI = ($, defaultLang = "es") => { class: joinClass("list bg-base-100 rounded-box shadow-md", className), }, [ - ui.If(header, () => $.html("li", { class: "p-4 pb-2 text-xs opacity-60 tracking-wide" }, [val(header)])), - ui.For(items, (item, index) => $.html("li", { class: "list-row" }, [render(item, index)]), keyFn), + $.if(header, () => $.html("li", { class: "p-4 pb-2 text-xs opacity-60 tracking-wide" }, [val(header)])), + $.for(items, (item, index) => $.html("li", { class: "list-row" }, [render(item, index)]), keyFn), ], ); }; @@ -1103,7 +1043,7 @@ export const UI = ($, defaultLang = "es") => { `timeline ${val(vertical) ? "timeline-vertical" : "timeline-horizontal"} ${val(compact) ? "timeline-compact" : ""} ${props.class || ""}`, }, [ - ui.For( + $.for( items, (item, i) => { const isFirst = i === 0; @@ -1179,51 +1119,59 @@ export const UI = ($, defaultLang = "es") => { if (!container) { container = $.html("div", { id: "sigpro-toast-container", - class: "fixed top-0 right-0 z-[9999] p-4 flex flex-col gap-2", + class: "fixed top-0 right-0 z-[9999] p-4 flex flex-col gap-2 pointer-events-none", }); document.body.appendChild(container); } - const runtime = $.view(() => { + const toastHost = $.html("div", { style: "display: contents" }); + container.appendChild(toastHost); + + let timeoutId; + const close = () => { + clearTimeout(timeoutId); + const el = toastHost.firstElementChild; + if (el && !el.classList.contains("opacity-0")) { + el.classList.add("translate-x-full", "opacity-0"); + setTimeout(() => { + instance.destroy(); + toastHost.remove(); + if (!container.hasChildNodes()) container.remove(); + }, 300); + } else { + instance.destroy(); + toastHost.remove(); + } + }; + + const ToastComponent = () => { const el = $.html( "div", { - class: `alert alert-soft ${type} shadow-lg transition-all duration-300 translate-x-10 opacity-0`, + class: `alert alert-soft ${type} shadow-lg transition-all duration-300 translate-x-10 opacity-0 pointer-events-auto`, }, [ - $.html("span", typeof message === "function" ? message() : message), - ui.Button( - { - class: "btn-xs btn-circle btn-ghost", - onclick: () => remove(), - }, - "✕", - ), + $.html("span", typeof message === "function" ? message : () => message), + ui.Button({ class: "btn-xs btn-circle btn-ghost", onclick: close }, "✕"), ], ); - const remove = () => { - el.classList.add("translate-x-full", "opacity-0"); - setTimeout(() => { - runtime.destroy(); - if (!container.hasChildNodes()) container.remove(); - }, 300); - }; - - setTimeout(remove, duration); + requestAnimationFrame(() => el.classList.remove("translate-x-10", "opacity-0")); return el; - }); + }; - container.appendChild(runtime.container); - requestAnimationFrame(() => { - const el = runtime.container.firstElementChild; - if (el) el.classList.remove("translate-x-10", "opacity-0"); - }); + const instance = $.mount(ToastComponent, toastHost); + + if (duration > 0) { + timeoutId = setTimeout(close, duration); + } + + return close; }; /** LOADING */ ui.Loading = (props) => { - return ui.If(props.$show, () => + return $.if(props.$show, () => $.html("div", { class: "fixed inset-0 z-[100] flex items-center justify-center backdrop-blur-sm bg-base-100/30" }, [ $.html("span", { class: "loading loading-spinner loading-lg text-primary" }), ]), diff --git a/UI/sigpro-ui.min.js b/UI/sigpro-ui.min.js index 5d09ade..e041a43 100644 --- a/UI/sigpro-ui.min.js +++ b/UI/sigpro-ui.min.js @@ -1 +1 @@ -export const UI=(t,e="es")=>{const l={},a={es:{close:"Cerrar",confirm:"Confirmar",cancel:"Cancelar",search:"Buscar...",loading:"Cargando..."},en:{close:"Close",confirm:"Confirm",cancel:"Cancel",search:"Search...",loading:"Loading..."}},s=t(e);l.SetLocale=t=>s(t);const n=t=>()=>a[s()][t]||t,A=t=>"function"==typeof t?t():t,o=(t,e)=>"function"==typeof e?()=>`${t} ${e()||""}`.trim():`${t} ${e||""}`.trim(),c="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAACLSURBVDiN7dO9CQJBFEXhb38K0FwQrMNEVpuwB0NjrcYabECsQk0sQ1mTF4zIjrgmBh54MMx998AEwzOrmC5e8gJjbDHCJO7PHYI0v2JT4Ig9DljGwq5DkOZTLOCOMoIhBpknpHmFWx3ldaaUo6oTc2/ab7rl+508f8GvCC5oenTn4tM1cWg/nBNmD4fBH/Kfvt2TAAAAAElFTkSuQmCC",r="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAnXAAAJ1wGxbhe3AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAASVJREFUOI190r0uhFEQBuBnVxaF2PUTCkFchV0SV6BQi0rEbShFlCqNktJP0Iqf3i3YVSlXVEQozojP8e2+ySSTed+ZMzNnKnpjCFPhv+C9j/YPlnCBV3TCujhHq19iFftoYxOjBa4esTb2QvsP+7jFWJ9HxnEXRf5gGU9Z8gKucBl+sUgHTahE8AJnOCoIT/AcmhmsF7gtrGINBqWFFWcmLXMUhzjIuEbk1GA+2i/DNh4wUsK1MVfFV2GUHJO4xlsPHr8j1Eu44bAcDek2agP4lDZaxWMm3MEKbrL4hjT/8U+gJc00nglnw4qYkL5xMW9rTzqSvEiefI/dMrIaRTrSPzcKXCNinUguPeUfNKWj6kqH9Bz+aVnbvb6PtKTp8F/wUSb6Bu5YN5n7ff0kAAAAAElFTkSuQmCC",i="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAnXAAAJ1wGxbhe3AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAQtJREFUOI2F0jFOAlEQBuAPImoFqyTa6TEEbfUihruYDYfwCAg3UDsTY20na0VjgqUWWuxgHsuy/skk82bmn/fPm9eyHXs4Cn+Br4baNZxjhk8UYUtMMWwitjHGHNfoJrlexObIo3YDY9zjoOGSQzxEkzVc4O0fctqkwCANzkJiE9LmI9ytDrvKB+tWGQnylIAsOB04VcrfdluO55CeYo6THfygVUne4jX8S1zho1LTDu7fCL2KxCe8oF8zUqb8G51VYGrzEffD6jDCJA0MY6bqnHXoK9d4Vk3kyk/S1KSPR9zUJdvRpAiJWZLLIlYEufYrrzBQ7nyJ97ClcuYN2dX1pejgOPwFvuuKfgHXiDR+HL1j1AAAAABJRU5ErkJggg==",d="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAnXAAAJ1wGxbhe3AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAARZJREFUOI2V0j1KQ1EQBeDPp4lWRiMoKVyAK9AoiLgJGytxD9oJNhKyDyvBnw2IugC3YGKVRk1KRbR48yC5vjzwwIHL3DPnzp2ZGdMxj9U4D/BZoZ3ANu4wQj84xC3aVYkZuujhCItjd42I9dAJ7R908YDlikeaeAyTCezgpST5IJia9LFVlA0nOMd7It4IjuMttKeFQR17uKooPcUV9lHL0ArX0T8MPqLa1hx+MDNFWDX7LHLV4/VGiWghmGJJvhu1WXzLO5rhORGeYRf3SfwQNVwWgbZ8SZqJcD04jhX5GDfTsjryJUlN0uQnXJRdZmHSx7H8nwWWItaP5NJVLrCFG3mTXoNDXJeVPW185E1ai/MAX2WiX9S3NSPYbj+uAAAAAElFTkSuQmCC",m="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAnXAAAJ1wGxbhe3AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAARJJREFUOI2l0r8uRFEQBvAfu9glwUYiUaxHUEl0VDpKeq+wpZBINAqFRHgTKg0tCSqVhmKDEM1u/Esodm725rq7iC+ZzMnM982ZmXP4JwpdchWsYBrXeMkj9XQQV3GEi+BMYR63v+mqiDPUUrEaTiP3I1ZxEOcySnE+jFxXVPEQPimWiCYzOdCbKbCFPe1Z+8PgBvvBycVMCIdSsY2wBEPBmcnrYBtraKRib2EJGljHjswLLuI8Z6SS9hLTl15iIR08wZLv2AzLYjk0YATP8n9lVWbrgUJohosYxCdG8Zghdvp5ldCUi6hrPd0VjvGEVzTxEYLkogGMYQ67uEtvcgKzGA8y9IV/D9/Evdb89Q7d/Q1fB8U0mpUmzV0AAAAASUVORK5CYII=";return l.If=(t,e,l=null)=>()=>{const a=A(t)?e:l;return"function"!=typeof a||a instanceof HTMLElement?a:a()},l.For=(e,l,a)=>{if("function"!=typeof a)throw new Error("SigPro UI: For requires a keyFn.");const s=document.createTextNode(""),n=t.html("div",{style:"display:contents"},[s]),o=new Map;return t.effect((()=>{const c=A(e)||[],r=new Set;c.forEach(((e,A)=>{const c=a(e,A);r.add(c);let i=o.get(c);i||(i=t.view((()=>l(e,A))),o.set(c,i)),n.insertBefore(i.container,s)})),o.forEach(((t,e)=>{r.has(e)||(t.destroy(),o.delete(e))}))})),n},l.Json=(t,e=2)=>Span({class:"font-mono whitespace-pre-wrap"},(()=>{try{return JSON.stringify(A(t),null,e)}catch(t){return"[Error: Circular or Invalid JSON]"}})),l.Request=(e,l=null,a={})=>{const s=t(null),n=t(!1),o=t(null),c=t(!1);let r=null;const i=async(t=null)=>{const i=A(e);if(i){r&&r.abort(),r=new AbortController,n(!0),o(null),c(!1);try{const e=t||l,A=await fetch(i,{method:a.method||(e?"POST":"GET"),headers:{"Content-Type":"application/json",...a.headers},body:e?JSON.stringify(e):null,signal:r.signal,...a});if(!A.ok)throw new Error(`HTTP ${A.status}`);let o=await A.json();"function"==typeof a.transform&&(o=a.transform(o)),s(o),c(!0)}catch(t){"AbortError"!==t.name&&o(t.message)}finally{n(!1)}}};return t((()=>(i(),()=>r?.abort()))),{data:s,loading:n,error:o,success:c,reload:t=>i(t)}},l.Response=(e,a)=>t.html("div",{class:"res-container"},[l.If(e.loading,t.html("div",{class:"flex justify-center p-4"},t.html("span",{class:"loading loading-dots text-primary"}))),l.If(e.error,(()=>t.html("div",{role:"alert",class:"alert alert-error"},[t.html("span",{},e.error()),l.Button({class:"btn-xs btn-ghost border-current",onclick:()=>e.reload()},"Retry")]))),l.If(e.success,(()=>{const t=e.data();return null!==t?a(t):null}))]),l.Button=(e,l)=>{const{badge:a,badgeClass:s,tooltip:n,icon:c,$loading:r,...i}=e;let d=t.html("button",{...i,class:o("btn",e.$class||e.class),$disabled:()=>A(r)||A(e.$disabled)||A(e.disabled)},[()=>A(r)?t.html("span",{class:"loading loading-spinner"}):null,c?t.html("span",{class:"mr-1"},c):null,l]);return a&&(d=t.html("div",{class:"indicator"},[t.html("span",{class:o("indicator-item badge",s||"badge-secondary")},a),d])),n?t.html("div",{class:"tooltip","data-tip":n},d):d},l.Input=e=>{const{label:l,tip:a,$value:s,$error:r,isSearch:i,icon:d,type:m="text",...h}=e,u="password"===m,g=t(!1),b={text:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAB2AAAAdgFOeyYIAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMRJREFUOI3t0bFKAmAUBeAPURD1HQwUTKPJEmzQoSWQcKpVfIuWdvU9WnqNhsYWBx0a2lvLSMKGbvQ7SO564HA497/3cu/92SPFAS5QDN9CftviDhZYYRpNPtH/rzATOsQT6jhCFzmc4DTJL6AX067hPiimuAr95RglzMJ/4AyyUXSMw3iEauhN6C0eUEMFAyzTFZ7xiOvwL3jbsPYSr3hPg3dB/o43SVYY+TnsPPwXztMG5SDr39dGM8kr4RKNDdPtJL4BNXEmsdKC+S4AAAAASUVORK5CYII=",password:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAWQAAAFkBqp2phgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAACQSURBVDiN7dKxDcJQDATQJ0YgXQQ1bAgDEIZBETPQwjakIjRQ8CMSyR8SiZKTrvHZd/r+JsYSNZrEI1ZR4ywzfElcJ55xwiITOECNTVDf4jDGoEEZ1Etcxxg8pmjRDiahb7BH20uKKPVUkVmL+YjQArdI+PT2bO9Pd/A34O71Rd9QeN/LAFUSckfUscWuG3oCgP8nrDH6T5AAAAAASUVORK5CYII=",date:c,number:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAB2AAAAdgFOeyYIAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMxJREFUOI3t0bFKwlEUBvBfmmBEr1APIDZJ9AJJQyAIvkGP0C4uQruza+DUmuIc9AC9gBG4Nmpkw/8IB3Vw1w8u95zvnvPde77LEeUUV9HAF67QRA2nmMf5A+o4x3cWOsMYy8j7WMX6jaYbLBL/mAWe8RcHm1ihs8G94gVKQQzwlAouMcQo8p/Y28HdYpYFZmsi0MVdxD1MdrxsC500wijdvgtbI1AYtDbxMwkuFAZmE1uYwkkSqOIaHyHcxEU0vUXNPSqKr37fZ6xDwD9DPS0OyHjQHQAAAABJRU5ErkJggg==",email:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAC4SURBVDiNxdIxagJRFIXhLzLFBNJYaJslSEylWOhq3IorMGQ16SyjYCFiZWU5pTaDFvOUyTAZ8RHID69555577oXLf/OEGaY4R3g/4IhORHg3eOXYYvSAeRQ8OWQYYoNPvDQYnxUr7zBB1grCAv3QbIlxjXmAb7Txhq+rkFUKq9NUU8vcJiizwDtOWGEdmvTKqT+61H0GXsP7jSxpEGF/R1e3wkO0FBeVRnhTSBTneBB3yvOI4D/mAnvrIwKM5s4AAAAAAElFTkSuQmCC"},p=t.html("input",{...h,type:()=>u?g()?"text":"password":m,placeholder:e.placeholder||l||(i?n("search")():" "),class:o("grow order-2 focus:outline-none",e.$class||e.class),$value:s,oninput:t=>{s?.(t.target.value),e.oninput?.(t)},$disabled:()=>A(e.$disabled)||A(e.disabled)}),f=d||(b[m]?t.html("img",{src:b[m],class:"w-5 h-5 opacity-50",alt:m}):null);return t.html("label",{class:()=>o("input input-bordered floating-label flex items-center gap-2 w-full relative",A(r)?"input-error":"")},[f?t.html("div",{class:"order-1 shrink-0"},f):null,l?t.html("span",{class:"text-base-content/60 order-0"},l):null,p,u?t.html("button",{type:"button",class:"order-3 btn btn-ghost btn-xs btn-circle opacity-50 hover:opacity-100",onclick:t=>{t.preventDefault(),g(!g())}},(()=>t.html("img",{class:"w-5 h-5",src:g()?"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADjSURBVDiN3dJNSgNBEAXgz4DZeAAVJ9tko2St3kaIFxAVt4KZeAD1GKKi7vQSydI/yHgALxAXU02GxniAFBR0v1ev+3V1sZSxjxtM8BM5wTX2/hNu4gFvOMI21iJ3cIwP3GMjF/dQ4RyraOMS34GPAmvjIrBeEnfwjoPGgSM8ooh8QtngB6Ep4BWnmaMqkY1LqqzmDC8tzNDK3/RHzLL9SloUYWfQIMuw3Yl8xrDBH6qbvZWALqbqBqVmlWF7GuKEDwPr5hbXcYdPnKBv/o39wL5wG7ULY1c9NGPzQRrjKrhli1/02zEjWyWMBwAAAABJRU5ErkJggg==":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAEDSURBVDiN1dK/K8VhFAbwD+VLGSxKcu9guSQ/Zils/gNkuaX4BxRZDTdklYU/QAaDlEVGGwu2Kz/uVbKJzWDwfuv1+jHz1Km3c85znuf0Hv4jxnD2W8MItnCJ5xAX2MQcHsOQL+jEAapYQD9aQwxiDy+B3JKSe1DHCpqQYQ0PeMJOpDyAmyAAirjGbDRwFYcoYCZSzjGP+8B1gqXEUT2QxyPlqaRnGceNeENzUswwil1MBocbSU9DCAXUUI6K25HtIo5QSVaooitP9OEO65iIbE+HXSvBVRbeNZQSR9pxGil3o83HNw5hEbfYR0dKFki5ci+u8OrzIQ1/R8xx7ocL+9t4B0HPOVXjoptxAAAAAElFTkSuQmCC"}))):null,a?t.html("div",{class:"tooltip tooltip-left order-4","data-tip":a},t.html("span",{class:"badge badge-ghost badge-xs cursor-help"},"?")):null,()=>A(r)?t.html("span",{class:"text-error text-[10px] absolute -bottom-5 left-2"},A(r)):null])},l.Select=e=>{const{label:a,options:s,$value:n,...c}=e,r=t.html("select",{...c,class:o("select select-bordered w-full",e.$class||e.class),$value:n,onchange:t=>n?.(t.target.value)},l.For((()=>A(s)||[]),(e=>t.html("option",{value:e.value,$selected:()=>String(A(n))===String(e.value)},e.label)),(t=>t.value)));return a?t.html("label",{class:"fieldset-label flex flex-col gap-1"},[t.html("span",{},a),r]):r},l.Autocomplete=e=>{const{options:a=[],$value:s,onSelect:o,label:c,placeholder:r,...i}=e,d=t(A(s)||""),m=t(!1),h=t(-1),u=t((()=>{const t=d().toLowerCase(),e=A(a)||[];return t?e.filter((e=>("string"==typeof e?e:e.label).toLowerCase().includes(t))):e})),g=t=>{const e="string"==typeof t?t:t.value,l="string"==typeof t?t:t.label;d(l),"function"==typeof s&&s(e),o?.(t),m(!1),h(-1)};return t.html("div",{class:"relative w-full"},[l.Input({label:c,placeholder:r||n("search")(),$value:d,onfocus:()=>m(!0),onblur:()=>setTimeout((()=>m(!1)),150),onkeydown:t=>{const e=u();"ArrowDown"===t.key?(t.preventDefault(),m(!0),h(Math.min(h()+1,e.length-1))):"ArrowUp"===t.key?(t.preventDefault(),h(Math.max(h()-1,0))):"Enter"===t.key&&h()>=0?(t.preventDefault(),g(e[h()])):"Escape"===t.key&&m(!1)},oninput:t=>{const e=t.target.value;d(e),"function"==typeof s&&s(e),m(!0),h(-1)},...i}),t.html("ul",{class:"absolute left-0 w-full menu bg-base-100 rounded-box mt-1 p-2 shadow-xl max-h-60 overflow-y-auto border border-base-300 z-50",style:()=>m()&&u().length?"display:block":"display:none"},[l.For(u,((e,l)=>t.html("li",{},[t.html("a",{class:()=>"block w-full "+(h()===l?"active bg-primary text-primary-content":""),onclick:()=>g(e),onmouseenter:()=>h(l)},"string"==typeof e?e:e.label)])),((t,e)=>("string"==typeof t?t:t.value)+e)),()=>u().length?null:t.html("li",{class:"p-2 text-center opacity-50"},"No results")])])},l.Datepicker=e=>{const{$value:a,range:s,label:n,placeholder:o,...r}=e,i=t(!1),d=t(new Date),m=t(null),h=()=>!0===A(s),u=new Date,g=`${u.getFullYear()}-${String(u.getMonth()+1).padStart(2,"0")}-${String(u.getDate()).padStart(2,"0")}`,b=t=>`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`,p=t=>{const e=b(t),l=A(a);if(h())if(!l?.start||l.start&&l.end)"function"==typeof a&&a({start:e,end:null});else{const t=l.start;"function"==typeof a&&a(e{const t=A(a);return t?"string"==typeof t?t:t.start&&t.end?`${t.start} - ${t.end}`:t.start?`${t.start}...`:"":""})),v=t=>{const e=d();d(new Date(e.getFullYear(),e.getMonth()+t,1))},B=t=>{const e=d();d(new Date(e.getFullYear()+t,e.getMonth(),1))};return t.html("div",{class:"relative w-full"},[l.Input({label:n,placeholder:o||(h()?"Seleccionar rango...":"Seleccionar fecha..."),$value:f,readonly:!0,icon:t.html("img",{src:c,class:"opacity-40"}),onclick:t=>{t.stopPropagation(),i(!i())},...r}),l.If(i,(()=>t.html("div",{class:"absolute left-0 mt-2 p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box z-[100] w-80 select-none",onclick:t=>t.stopPropagation()},[t.html("div",{class:"flex justify-between items-center mb-4 gap-1"},[t.html("div",{class:"flex gap-0.5"},[t.html("button",{type:"button",class:"btn btn-ghost btn-xs px-1",onclick:()=>B(-1)},t.html("img",{src:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABlSURBVDiN3ZLBDUBAEEUfmtCchA5woUMlOO1FCQrAwbqwf8eFhHd7mfzJn2Tg82TGvABywAmPUgOLD4XcDK9AJ/y5cOlrNsIvpCdPDL/FUbkX/t6Slv3+SjgQf6QBmIAZGAP+FzZJViOd89x8pAAAAABJRU5ErkJggg==",class:"opacity-40"})),t.html("button",{type:"button",class:"btn btn-ghost btn-xs px-1",onclick:()=>v(-1)},t.html("img",{src:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABfSURBVDiNY2AY8oCZSHWxDAwMEgwMDHfJsaSAgYHhH9QQsjT/Z2BgKKe75gQGiLMLCSlkwiHOSI6t6ADmhYoBN6SIARIeidgkiUlIxxkYGB4xMDB8YmBguE6JSwYpAACvLRHTKwPjZgAAAABJRU5ErkJggg==",class:"opacity-40"}))]),t.html("span",{class:"font-bold uppercase flex-1 text-center"},[()=>d().toLocaleString("es-ES",{month:"short",year:"numeric"})]),t.html("div",{class:"flex gap-0.5"},[t.html("button",{type:"button",class:"btn btn-ghost btn-xs px-1",onclick:()=>v(1)},t.html("img",{src:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABNSURBVDiN3dAxCoAwFATRh3fU2oAHiDbi5Y1F2jT+gKLbzyy7/DYjUo8g4cTWI8koOF6XrOqc5ifDDVGJthfsj8OLujtHYJgwR+GP5QKMxA9/SolDQgAAAABJRU5ErkJggg==",class:"opacity-40"})),t.html("button",{type:"button",class:"btn btn-ghost btn-xs px-1",onclick:()=>B(1)},t.html("img",{src:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABmSURBVDiN3dGxCoAgEMbxfz1dL1BTREJzmUv08trgDYcg6VCD3/YD7zvkoLmMgFEegLmmwAAecOJVvNeUWCAAt7IHjt9LThkyiRf9qC8oCom70u0BuDL+bngj/tNm/JqJePucW8wDvGYdzT0nMUkAAAAASUVORK5CYII=",class:"opacity-40"}))])]),t.html("div",{class:"grid grid-cols-7 gap-1",onmouseleave:()=>m(null)},[...["L","M","X","J","V","S","D"].map((e=>t.html("div",{class:"text-[10px] opacity-40 font-bold text-center"},e))),()=>{const e=d(),l=e.getFullYear(),s=e.getMonth(),n=new Date(l,s,1).getDay(),o=0===n?6:n-1,c=new Date(l,s+1,0).getDate(),r=[];for(let e=0;e{const t=A(a),e=m(),l="string"==typeof t?t===o:t?.start===o,s=t?.end===o;let n=!1;if(h()&&t?.start){const l=t.start;!t.end&&e?n=o>l&&o<=e||o=e:t.end&&(n=o>l&&o{h()&&m(o)},onclick:()=>p(n)},[e.toString()]))}return r}])]))),l.If(i,(()=>t.html("div",{class:"fixed inset-0 z-[90]",onclick:()=>i(!1)})))])},l.Colorpicker=e=>{const{$value:a,label:s,...n}=e,o=t(!1),c=["#000","#1A1A1A","#333","#4D4D4D","#666","#808080","#B3B3B3","#FFF","#450a0a","#7f1d1d","#991b1b","#b91c1c","#dc2626","#ef4444","#f87171","#fca5a5","#431407","#7c2d12","#9a3412","#c2410c","#ea580c","#f97316","#fb923c","#ffedd5","#713f12","#a16207","#ca8a04","#eab308","#facc15","#fde047","#fef08a","#fff9c4","#064e3b","#065f46","#059669","#10b981","#34d399","#4ade80","#84cc16","#d9f99d","#082f49","#075985","#0284c7","#0ea5e9","#38bdf8","#7dd3fc","#22d3ee","#cffafe","#1e1b4b","#312e81","#4338ca","#4f46e5","#6366f1","#818cf8","#a5b4fc","#e0e7ff","#2e1065","#4c1d95","#6d28d9","#7c3aed","#8b5cf6","#a855f7","#d946ef","#fae8ff"],r=()=>A(a)||"#000000";return t.html("div",{class:"relative w-fit"},[t.html("button",{type:"button",class:"btn px-3 bg-base-100 border-base-300 hover:border-primary/50 flex items-center gap-2 shadow-sm font-normal normal-case",onclick:t=>{t.stopPropagation(),o(!o())},...n},[t.html("div",{class:"size-5 rounded-sm shadow-inner border border-black/10 shrink-0",style:()=>`background-color: ${r()}`}),s?t.html("span",{class:"opacity-80"},s):null]),l.If(o,(()=>t.html("div",{class:"absolute left-0 mt-2 p-3 bg-base-100 border border-base-300 shadow-2xl rounded-box z-[110] w-64 select-none",onclick:t=>t.stopPropagation()},[t.html("div",{class:"grid grid-cols-8 gap-1"},c.map((e=>t.html("button",{type:"button",style:`background-color: ${e}`,class:()=>"size-6 rounded-sm cursor-pointer transition-all hover:scale-125 hover:z-10 active:scale-95 outline-none border border-black/5 \n "+(r().toLowerCase()===e.toLowerCase()?"ring-2 ring-offset-1 ring-primary z-10 scale-110":""),onclick:()=>{a(e),o(!1)}}))))]))),l.If(o,(()=>t.html("div",{class:"fixed inset-0 z-[100]",onclick:()=>o(!1)})))])},l.CheckBox=e=>{const{$value:l,tooltip:a,toggle:s,...n}=e,o=t.html("input",{...n,type:"checkbox",class:()=>A(s)?"toggle":"checkbox",$checked:l,onchange:t=>l?.(t.target.checked)}),c=t.html("label",{class:"label cursor-pointer justify-start gap-3"},[o,e.label?t.html("span",{class:"label-text"},e.label):null]);return a?t.html("div",{class:"tooltip","data-tip":a},c):c},l.Radio=e=>{const{label:l,tooltip:a,$value:s,value:n,...c}=e,r=t.html("input",{...c,type:"radio",class:o("radio",e.$class||e.class),$checked:()=>A(s)===n,$disabled:()=>A(e.$disabled)||A(e.disabled),onclick:()=>"function"==typeof s&&s(n)});if(!l&&!a)return r;const i=t.html("label",{class:"label cursor-pointer justify-start gap-3"},[r,l?t.html("span",{class:"label-text"},l):null]);return a?t.html("div",{class:"tooltip","data-tip":a},i):i},l.Range=e=>{const{label:l,tooltip:a,$value:s,...n}=e,c=t.html("input",{...n,type:"range",class:o("range",e.$class||e.class),$value:s,$disabled:()=>A(e.$disabled)||A(e.disabled),oninput:t=>"function"==typeof s&&s(t.target.value)});if(!l&&!a)return c;const r=t.html("div",{class:"flex flex-col gap-2"},[l?t.html("span",{class:"label-text"},l):null,c]);return a?t.html("div",{class:"tooltip","data-tip":a},r):r},l.Modal=(e,a)=>{const{title:s,buttons:A,$open:o,...c}=e,r=()=>o(!1);return l.If(o,(()=>t.html("dialog",{...c,class:"modal modal-open"},[t.html("div",{class:"modal-box"},[s?t.html("h3",{class:"text-lg font-bold mb-4"},s):null,"function"==typeof a?a():a,t.html("div",{class:"modal-action flex gap-2"},[...(Array.isArray(A)?A:[A]).filter(Boolean),l.Button({onclick:r},n("close")())])]),t.html("form",{method:"dialog",class:"modal-backdrop",onclick:t=>(t.preventDefault(),r())},[t.html("button",{},"close")])])))},l.Grid=e=>{const{data:l,options:a,class:s}=e;let n=null;const o=t.html("div",{style:"height: 100%; width: 100%;",class:s}),c=new MutationObserver((()=>{n&&n.setGridOption("theme",getTheme(isDark()))}));c.observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme"]}),o._cleanups.add((()=>c.disconnect()));const r=t.effect((()=>{const t=isDark(),e=getTheme(t),s=A(l)||[];n?n.setGridOption("theme",e):n=createGrid(o,{...A(a)||{},theme:e,rowData:s})}));o._cleanups.add(r);const i=t.effect((()=>{const t=A(l);n&&Array.isArray(t)&&n.setGridOption("rowData",t)}));return o._cleanups.add(i),o._cleanups.add((()=>{n&&(n.destroy(),n=null)})),o},l.Dropdown=(e,l)=>{const{label:a,icon:s,...n}=e;return t.html("div",{...n,class:()=>`dropdown ${A(e.$class)||e.class||""}`},[t.html("div",{tabindex:0,role:"button",class:"btn m-1 flex items-center gap-2"},[s?"function"==typeof s?s():s:null,a?"function"==typeof a?a():a:null]),t.html("ul",{tabindex:0,class:"dropdown-content z-[50] menu p-2 shadow bg-base-100 rounded-box min-w-max border border-base-300"},["function"==typeof l?l():l])])},l.Accordion=(e,l)=>{const{title:a,name:s,$open:n,open:c,...r}=e;return t.html("div",{...r,class:o("collapse collapse-arrow bg-base-200 mb-2",e.$class||e.class)},[t.html("input",{type:s?"radio":"checkbox",name:s,$checked:()=>A(n)||A(c),onchange:t=>"function"==typeof n&&n(t.target.checked)}),t.html("div",{class:"collapse-title text-xl font-medium"},a),t.html("div",{class:"collapse-content"},l)])},l.Tabs=e=>{const{items:a,...s}=e,n="function"==typeof a?a:()=>a||[];return t.html("div",{...s,class:"flex flex-col gap-4 w-full"},[t.html("div",{role:"tablist",class:o("tabs tabs-box",e.$class||e.class)},l.For(n,(e=>t.html("a",{role:"tab",class:()=>o("tab",A(e.active)&&"tab-active",A(e.disabled),e.tip),"data-tip":e.tip,onclick:t=>!A(e.disabled)&&e.onclick?.(t)},e.label)),(t=>t.label))),()=>{const e=n().find((t=>A(t.active)));if(!e)return null;const l=A(e.content);return t.html("div",{class:"p-4"},["function"==typeof l?l():l])}])},l.Badge=(e,l)=>t.html("span",{...e,class:o("badge",e.$class||e.class)},l),l.Tooltip=(e,l)=>t.html("div",{...e,class:o("tooltip",e.$class||e.class),"data-tip":e.tip},l),l.Navbar=(e,l)=>t.html("div",{...e,class:o("navbar bg-base-100 shadow-sm px-4",e.$class||e.class)},l),l.Menu=e=>{const a=e=>l.For((()=>e||[]),(e=>t.html("li",{},[e.children?t.html("details",{open:e.open},[t.html("summary",{},[e.icon&&t.html("span",{class:"mr-2"},e.icon),e.label]),t.html("ul",{},a(e.children))]):t.html("a",{class:()=>A(e.active)?"active":"",onclick:e.onclick},[e.icon&&t.html("span",{class:"mr-2"},e.icon),e.label])])),((t,e)=>t.label||e));return t.html("ul",{...e,class:o("menu bg-base-200 rounded-box",e.$class||e.class)},a(e.items))},l.Drawer=e=>t.html("div",{class:o("drawer",e.$class||e.class)},[t.html("input",{id:e.id,type:"checkbox",class:"drawer-toggle",$checked:e.$open}),t.html("div",{class:"drawer-content"},e.content),t.html("div",{class:"drawer-side"},[t.html("label",{for:e.id,class:"drawer-overlay",onclick:()=>e.$open?.(!1)}),t.html("div",{class:"min-h-full bg-base-200 w-80"},e.side)])]),l.Fieldset=(e,l)=>t.html("fieldset",{...e,class:o("fieldset bg-base-200 border border-base-300 p-4 rounded-lg",e.$class||e.class)},[()=>{const l=A(e.legend);return l?t.html("legend",{class:"fieldset-legend font-bold"},[l]):null},l]),l.List=e=>{const{items:a,header:s,render:n,keyFn:c,class:r}=e;return t.html("ul",{class:o("list bg-base-100 rounded-box shadow-md",r)},[l.If(s,(()=>t.html("li",{class:"p-4 pb-2 text-xs opacity-60 tracking-wide"},[A(s)]))),l.For(a,((e,l)=>t.html("li",{class:"list-row"},[n(e,l)])),c)])},l.Stack=(e,l)=>t.html("div",{...e,class:o("stack",e.$class||e.class)},l),l.Stat=e=>t.html("div",{...e,class:o("stat",e.$class||e.class)},[e.icon&&t.html("div",{class:"stat-figure text-secondary"},e.icon),e.label&&t.html("div",{class:"stat-title"},e.label),t.html("div",{class:"stat-value"},(()=>A(e.$value)??e.value)),e.desc&&t.html("div",{class:"stat-desc"},e.desc)]),l.Swap=e=>t.html("label",{class:o("swap",e.$class||e.class)},[t.html("input",{type:"checkbox",$checked:e.$value,onchange:t=>e.$value?.(t.target.checked)}),t.html("div",{class:"swap-on"},e.on),t.html("div",{class:"swap-off"},e.off)]),l.Indicator=(e,l)=>t.html("div",{class:o("indicator",e.$class||e.class)},[l,t.html("span",{class:o("indicator-item badge",e.badgeClass)},e.badge)]),l.Rating=e=>{const{$value:l,count:a=5,name:s=`rating-${Math.random().toString(36).slice(2,7)}`,mask:n="mask-star",readonly:o=!1,...c}=e;return t.html("div",{...c,class:()=>`rating ${A(o)?"pointer-events-none":""} ${e.class||""}`},Array.from({length:A(a)},((e,a)=>{const c=a+1;return t.html("input",{type:"radio",name:s,class:`mask ${n}`,"aria-label":`${c} star`,checked:()=>Math.round(A(l))===c,onchange:()=>{A(o)||"function"!=typeof l||l(c)}})})))},l.Alert=(e,l)=>{const{type:a="info",soft:s=!0,...n}=e,o={info:r,success:i,warning:m,error:d},c=l||e.message;return t.html("div",{...n,role:"alert",class:()=>`alert ${(()=>{const t=A(a);return{info:"alert-info",success:"alert-success",warning:"alert-warning",error:"alert-error"}[t]||t})()} ${A(s)?"alert-soft":""} ${e.class||""}`},[t.html("img",{src:o[A(a)]||o.info,class:"w-4 h-4 object-contain",alt:A(a)}),t.html("div",{class:"flex-1"},[t.html("span",{},["function"==typeof c?c():c])]),e.actions?t.html("div",{class:"flex-none"},["function"==typeof e.actions?e.actions():e.actions]):null])},l.Timeline=e=>{const{items:a=[],vertical:s=!0,compact:n=!1,...o}=e,c={info:r,success:i,warning:m,error:d};return t.html("ul",{...o,class:()=>`timeline ${A(s)?"timeline-vertical":"timeline-horizontal"} ${A(n)?"timeline-compact":""} ${e.class||""}`},[l.For(a,((e,l)=>{const s=0===l,n=l===A(a).length-1,o=e.type||"success",r=t=>"function"==typeof t?t():t;return t.html("li",{class:"flex-1"},[s?null:t.html("hr",{class:e.completed?"bg-primary":""}),t.html("div",{class:"timeline-start"},[r(e.title)]),t.html("div",{class:"timeline-middle"},[t.html("img",{src:c[o]||e.icon||c.success,class:"w-4 h-4 object-contain mx-1",alt:o})]),t.html("div",{class:"timeline-end timeline-box shadow-sm"},[r(e.detail)]),n?null:t.html("hr",{class:e.completed?"bg-primary":""})])}),((t,e)=>t.id||e))])},l.Fab=e=>{const{icon:l,label:a,actions:s=[],position:n="bottom-6 right-6",...o}=e;return t.html("div",{...o,class:()=>`fab fixed ${A(n)} flex flex-col-reverse items-end gap-3 z-[100] ${e.class||""}`},[t.html("div",{tabindex:0,role:"button",class:"btn btn-lg btn-circle btn-primary shadow-2xl"},[l?"function"==typeof l?l():l:null,!l&&a?a:null]),...A(s).map((e=>t.html("div",{class:"flex items-center gap-3 transition-all duration-300"},[e.label?t.html("span",{class:"badge badge-ghost shadow-sm whitespace-nowrap"},e.label):null,t.html("button",{type:"button",class:`btn btn-circle shadow-lg ${e.class||""}`,onclick:t=>{t.stopPropagation(),e.onclick?.(t)}},[e.icon?"function"==typeof e.icon?e.icon():e.icon:e.text||""])])))])},l.Toast=(e,a="alert-success",s=3500)=>{let n=document.getElementById("sigpro-toast-container");n||(n=t.html("div",{id:"sigpro-toast-container",class:"fixed top-0 right-0 z-[9999] p-4 flex flex-col gap-2"}),document.body.appendChild(n));const A=t.view((()=>{const o=t.html("div",{class:`alert alert-soft ${a} shadow-lg transition-all duration-300 translate-x-10 opacity-0`},[t.html("span","function"==typeof e?e():e),l.Button({class:"btn-xs btn-circle btn-ghost",onclick:()=>c()},"✕")]),c=()=>{o.classList.add("translate-x-full","opacity-0"),setTimeout((()=>{A.destroy(),n.hasChildNodes()||n.remove()}),300)};return setTimeout(c,s),o}));n.appendChild(A.container),requestAnimationFrame((()=>{const t=A.container.firstElementChild;t&&t.classList.remove("translate-x-10","opacity-0")}))},l.Loading=e=>l.If(e.$show,(()=>t.html("div",{class:"fixed inset-0 z-[100] flex items-center justify-center backdrop-blur-sm bg-base-100/30"},[t.html("span",{class:"loading loading-spinner loading-lg text-primary"})]))),l.tt=n,Object.keys(l).forEach((e=>{window[e]=l[e],t[e]=l[e]})),l}; \ No newline at end of file +export const UI=(t,e="es")=>{const l={},a={es:{close:"Cerrar",confirm:"Confirmar",cancel:"Cancelar",search:"Buscar...",loading:"Cargando..."},en:{close:"Close",confirm:"Confirm",cancel:"Cancel",search:"Search...",loading:"Loading..."}},s=t(e);l.SetLocale=t=>s(t);const n=t=>()=>a[s()][t]||t,A=t=>"function"==typeof t?t():t,o=(t,e)=>"function"==typeof e?()=>`${t} ${e()||""}`.trim():`${t} ${e||""}`.trim(),c="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAACLSURBVDiN7dO9CQJBFEXhb38K0FwQrMNEVpuwB0NjrcYabECsQk0sQ1mTF4zIjrgmBh54MMx998AEwzOrmC5e8gJjbDHCJO7PHYI0v2JT4Ig9DljGwq5DkOZTLOCOMoIhBpknpHmFWx3ldaaUo6oTc2/ab7rl+508f8GvCC5oenTn4tM1cWg/nBNmD4fBH/Kfvt2TAAAAAElFTkSuQmCC",i="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAnXAAAJ1wGxbhe3AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAASVJREFUOI190r0uhFEQBuBnVxaF2PUTCkFchV0SV6BQi0rEbShFlCqNktJP0Iqf3i3YVSlXVEQozojP8e2+ySSTed+ZMzNnKnpjCFPhv+C9j/YPlnCBV3TCujhHq19iFftoYxOjBa4esTb2QvsP+7jFWJ9HxnEXRf5gGU9Z8gKucBl+sUgHTahE8AJnOCoIT/AcmhmsF7gtrGINBqWFFWcmLXMUhzjIuEbk1GA+2i/DNh4wUsK1MVfFV2GUHJO4xlsPHr8j1Eu44bAcDek2agP4lDZaxWMm3MEKbrL4hjT/8U+gJc00nglnw4qYkL5xMW9rTzqSvEiefI/dMrIaRTrSPzcKXCNinUguPeUfNKWj6kqH9Bz+aVnbvb6PtKTp8F/wUSb6Bu5YN5n7ff0kAAAAAElFTkSuQmCC",r="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAnXAAAJ1wGxbhe3AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAQtJREFUOI2F0jFOAlEQBuAPImoFqyTa6TEEbfUihruYDYfwCAg3UDsTY20na0VjgqUWWuxgHsuy/skk82bmn/fPm9eyHXs4Cn+Br4baNZxjhk8UYUtMMWwitjHGHNfoJrlexObIo3YDY9zjoOGSQzxEkzVc4O0fctqkwCANzkJiE9LmI9ytDrvKB+tWGQnylIAsOB04VcrfdluO55CeYo6THfygVUne4jX8S1zho1LTDu7fCL2KxCe8oF8zUqb8G51VYGrzEffD6jDCJA0MY6bqnHXoK9d4Vk3kyk/S1KSPR9zUJdvRpAiJWZLLIlYEufYrrzBQ7nyJ97ClcuYN2dX1pejgOPwFvuuKfgHXiDR+HL1j1AAAAABJRU5ErkJggg==",d="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAnXAAAJ1wGxbhe3AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAARZJREFUOI2V0j1KQ1EQBeDPp4lWRiMoKVyAK9AoiLgJGytxD9oJNhKyDyvBnw2IugC3YGKVRk1KRbR48yC5vjzwwIHL3DPnzp2ZGdMxj9U4D/BZoZ3ANu4wQj84xC3aVYkZuujhCItjd42I9dAJ7R908YDlikeaeAyTCezgpST5IJia9LFVlA0nOMd7It4IjuMttKeFQR17uKooPcUV9lHL0ArX0T8MPqLa1hx+MDNFWDX7LHLV4/VGiWghmGJJvhu1WXzLO5rhORGeYRf3SfwQNVwWgbZ8SZqJcD04jhX5GDfTsjryJUlN0uQnXJRdZmHSx7H8nwWWItaP5NJVLrCFG3mTXoNDXJeVPW185E1ai/MAX2WiX9S3NSPYbj+uAAAAAElFTkSuQmCC",m="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAnXAAAJ1wGxbhe3AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAARJJREFUOI2l0r8uRFEQBvAfu9glwUYiUaxHUEl0VDpKeq+wpZBINAqFRHgTKg0tCSqVhmKDEM1u/Esodm725rq7iC+ZzMnM982ZmXP4JwpdchWsYBrXeMkj9XQQV3GEi+BMYR63v+mqiDPUUrEaTiP3I1ZxEOcySnE+jFxXVPEQPimWiCYzOdCbKbCFPe1Z+8PgBvvBycVMCIdSsY2wBEPBmcnrYBtraKRib2EJGljHjswLLuI8Z6SS9hLTl15iIR08wZLv2AzLYjk0YATP8n9lVWbrgUJohosYxCdG8Zghdvp5ldCUi6hrPd0VjvGEVzTxEYLkogGMYQ67uEtvcgKzGA8y9IV/D9/Evdb89Q7d/Q1fB8U0mpUmzV0AAAAASUVORK5CYII=";return l.Request=(e,l=null,a={})=>{const s=t(null),n=t(!1),o=t(null),c=t(!1);let i=null;const r=async(t=null)=>{const r=A(e);if(r){i&&i.abort(),i=new AbortController,n(!0),o(null),c(!1);try{const e=t||l,A=await fetch(r,{method:a.method||(e?"POST":"GET"),headers:{"Content-Type":"application/json",...a.headers},body:e?JSON.stringify(e):null,signal:i.signal,...a});if(!A.ok)throw new Error(`HTTP ${A.status}`);let o=await A.json();"function"==typeof a.transform&&(o=a.transform(o)),s(o),c(!0)}catch(t){"AbortError"!==t.name&&o(t.message)}finally{n(!1)}}};return t((()=>(r(),()=>i?.abort()))),{data:s,loading:n,error:o,success:c,reload:t=>r(t)}},l.Response=(e,a)=>t.html("div",{class:"res-container"},[t.if(e.loading,t.html("div",{class:"flex justify-center p-4"},t.html("span",{class:"loading loading-dots text-primary"}))),t.if(e.error,(()=>t.html("div",{role:"alert",class:"alert alert-error"},[t.html("span",{},e.error()),l.Button({class:"btn-xs btn-ghost border-current",onclick:()=>e.reload()},"Retry")]))),t.if(e.success,(()=>{const t=e.data();return null!==t?a(t):null}))]),l.Button=(e,l)=>{const{badge:a,badgeClass:s,tooltip:n,icon:c,$loading:i,...r}=e;let d=t.html("button",{...r,class:o("btn",e.$class||e.class),$disabled:()=>A(i)||A(e.$disabled)||A(e.disabled)},[()=>A(i)?t.html("span",{class:"loading loading-spinner"}):null,c?t.html("span",{class:"mr-1"},c):null,l]);return a&&(d=t.html("div",{class:"indicator"},[t.html("span",{class:o("indicator-item badge",s||"badge-secondary")},a),d])),n?t.html("div",{class:"tooltip","data-tip":n},d):d},l.Input=e=>{const{label:l,tip:a,$value:s,$error:i,isSearch:r,icon:d,type:m="text",...h}=e,u="password"===m,g=t(!1),b={text:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAB2AAAAdgFOeyYIAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMRJREFUOI3t0bFKAmAUBeAPURD1HQwUTKPJEmzQoSWQcKpVfIuWdvU9WnqNhsYWBx0a2lvLSMKGbvQ7SO564HA497/3cu/92SPFAS5QDN9CftviDhZYYRpNPtH/rzATOsQT6jhCFzmc4DTJL6AX067hPiimuAr95RglzMJ/4AyyUXSMw3iEauhN6C0eUEMFAyzTFZ7xiOvwL3jbsPYSr3hPg3dB/o43SVYY+TnsPPwXztMG5SDr39dGM8kr4RKNDdPtJL4BNXEmsdKC+S4AAAAASUVORK5CYII=",password:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAWQAAAFkBqp2phgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAACQSURBVDiN7dKxDcJQDATQJ0YgXQQ1bAgDEIZBETPQwjakIjRQ8CMSyR8SiZKTrvHZd/r+JsYSNZrEI1ZR4ywzfElcJ55xwiITOECNTVDf4jDGoEEZ1Etcxxg8pmjRDiahb7BH20uKKPVUkVmL+YjQArdI+PT2bO9Pd/A34O71Rd9QeN/LAFUSckfUscWuG3oCgP8nrDH6T5AAAAAASUVORK5CYII=",date:c,number:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAB2AAAAdgFOeyYIAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMxJREFUOI3t0bFKwlEUBvBfmmBEr1APIDZJ9AJJQyAIvkGP0C4uQruza+DUmuIc9AC9gBG4Nmpkw/8IB3Vw1w8u95zvnvPde77LEeUUV9HAF67QRA2nmMf5A+o4x3cWOsMYy8j7WMX6jaYbLBL/mAWe8RcHm1ihs8G94gVKQQzwlAouMcQo8p/Y28HdYpYFZmsi0MVdxD1MdrxsC500wijdvgtbI1AYtDbxMwkuFAZmE1uYwkkSqOIaHyHcxEU0vUXNPSqKr37fZ6xDwD9DPS0OyHjQHQAAAABJRU5ErkJggg==",email:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAC4SURBVDiNxdIxagJRFIXhLzLFBNJYaJslSEylWOhq3IorMGQ16SyjYCFiZWU5pTaDFvOUyTAZ8RHID69555577oXLf/OEGaY4R3g/4IhORHg3eOXYYvSAeRQ8OWQYYoNPvDQYnxUr7zBB1grCAv3QbIlxjXmAb7Txhq+rkFUKq9NUU8vcJiizwDtOWGEdmvTKqT+61H0GXsP7jSxpEGF/R1e3wkO0FBeVRnhTSBTneBB3yvOI4D/mAnvrIwKM5s4AAAAAAElFTkSuQmCC"},p=t.html("input",{...h,type:()=>u?g()?"text":"password":m,placeholder:e.placeholder||l||(r?n("search")():" "),class:o("grow order-2 focus:outline-none",e.$class||e.class),$value:s,oninput:t=>{s?.(t.target.value),e.oninput?.(t)},$disabled:()=>A(e.$disabled)||A(e.disabled)}),f=d||(b[m]?t.html("img",{src:b[m],class:"w-5 h-5 opacity-50",alt:m}):null);return t.html("label",{class:()=>o("input input-bordered floating-label flex items-center gap-2 w-full relative",A(i)?"input-error":"")},[f?t.html("div",{class:"order-1 shrink-0"},f):null,l?t.html("span",{class:"text-base-content/60 order-0"},l):null,p,u?t.html("button",{type:"button",class:"order-3 btn btn-ghost btn-xs btn-circle opacity-50 hover:opacity-100",onclick:t=>{t.preventDefault(),g(!g())}},(()=>t.html("img",{class:"w-5 h-5",src:g()?"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADjSURBVDiN3dJNSgNBEAXgz4DZeAAVJ9tko2St3kaIFxAVt4KZeAD1GKKi7vQSydI/yHgALxAXU02GxniAFBR0v1ev+3V1sZSxjxtM8BM5wTX2/hNu4gFvOMI21iJ3cIwP3GMjF/dQ4RyraOMS34GPAmvjIrBeEnfwjoPGgSM8ooh8QtngB6Ep4BWnmaMqkY1LqqzmDC8tzNDK3/RHzLL9SloUYWfQIMuw3Yl8xrDBH6qbvZWALqbqBqVmlWF7GuKEDwPr5hbXcYdPnKBv/o39wL5wG7ULY1c9NGPzQRrjKrhli1/02zEjWyWMBwAAAABJRU5ErkJggg==":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAEDSURBVDiN1dK/K8VhFAbwD+VLGSxKcu9guSQ/Zils/gNkuaX4BxRZDTdklYU/QAaDlEVGGwu2Kz/uVbKJzWDwfuv1+jHz1Km3c85znuf0Hv4jxnD2W8MItnCJ5xAX2MQcHsOQL+jEAapYQD9aQwxiDy+B3JKSe1DHCpqQYQ0PeMJOpDyAmyAAirjGbDRwFYcoYCZSzjGP+8B1gqXEUT2QxyPlqaRnGceNeENzUswwil1MBocbSU9DCAXUUI6K25HtIo5QSVaooitP9OEO65iIbE+HXSvBVRbeNZQSR9pxGil3o83HNw5hEbfYR0dKFki5ci+u8OrzIQ1/R8xx7ocL+9t4B0HPOVXjoptxAAAAAElFTkSuQmCC"}))):null,a?t.html("div",{class:"tooltip tooltip-left order-4","data-tip":a},t.html("span",{class:"badge badge-ghost badge-xs cursor-help"},"?")):null,()=>A(i)?t.html("span",{class:"text-error text-[10px] absolute -bottom-5 left-2"},A(i)):null])},l.Select=e=>{const{label:l,options:a,$value:s,...n}=e,c=t.html("select",{...n,class:o("select select-bordered w-full",e.$class||e.class),$value:s,onchange:t=>s?.(t.target.value)},t.for((()=>A(a)||[]),(e=>t.html("option",{value:e.value,$selected:()=>String(A(s))===String(e.value)},e.label)),(t=>t.value)));return l?t.html("label",{class:"fieldset-label flex flex-col gap-1"},[t.html("span",{},l),c]):c},l.Autocomplete=e=>{const{options:a=[],$value:s,onSelect:o,label:c,placeholder:i,...r}=e,d=t(A(s)||""),m=t(!1),h=t(-1),u=t((()=>{const t=d().toLowerCase(),e=A(a)||[];return t?e.filter((e=>("string"==typeof e?e:e.label).toLowerCase().includes(t))):e})),g=t=>{const e="string"==typeof t?t:t.value,l="string"==typeof t?t:t.label;d(l),"function"==typeof s&&s(e),o?.(t),m(!1),h(-1)};return t.html("div",{class:"relative w-full"},[l.Input({label:c,placeholder:i||n("search")(),$value:d,onfocus:()=>m(!0),onblur:()=>setTimeout((()=>m(!1)),150),onkeydown:t=>{const e=u();"ArrowDown"===t.key?(t.preventDefault(),m(!0),h(Math.min(h()+1,e.length-1))):"ArrowUp"===t.key?(t.preventDefault(),h(Math.max(h()-1,0))):"Enter"===t.key&&h()>=0?(t.preventDefault(),g(e[h()])):"Escape"===t.key&&m(!1)},oninput:t=>{const e=t.target.value;d(e),"function"==typeof s&&s(e),m(!0),h(-1)},...r}),t.html("ul",{class:"absolute left-0 w-full menu bg-base-100 rounded-box mt-1 p-2 shadow-xl max-h-60 overflow-y-auto border border-base-300 z-50",style:()=>m()&&u().length?"display:block":"display:none"},[t.for(u,((e,l)=>t.html("li",{},[t.html("a",{class:()=>"block w-full "+(h()===l?"active bg-primary text-primary-content":""),onclick:()=>g(e),onmouseenter:()=>h(l)},"string"==typeof e?e:e.label)])),((t,e)=>("string"==typeof t?t:t.value)+e)),()=>u().length?null:t.html("li",{class:"p-2 text-center opacity-50"},"No results")])])},l.Datepicker=e=>{const{$value:a,range:s,label:n,placeholder:o,...i}=e,r=t(!1),d=t(new Date),m=t(null),h=()=>!0===A(s),u=new Date,g=`${u.getFullYear()}-${String(u.getMonth()+1).padStart(2,"0")}-${String(u.getDate()).padStart(2,"0")}`,b=t=>`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`,p=t=>{const e=b(t),l=A(a);if(h())if(!l?.start||l.start&&l.end)"function"==typeof a&&a({start:e,end:null});else{const t=l.start;"function"==typeof a&&a(e{const t=A(a);return t?"string"==typeof t?t:t.start&&t.end?`${t.start} - ${t.end}`:t.start?`${t.start}...`:"":""})),v=t=>{const e=d();d(new Date(e.getFullYear(),e.getMonth()+t,1))},B=t=>{const e=d();d(new Date(e.getFullYear()+t,e.getMonth(),1))};return t.html("div",{class:"relative w-full"},[l.Input({label:n,placeholder:o||(h()?"Seleccionar rango...":"Seleccionar fecha..."),$value:f,readonly:!0,icon:t.html("img",{src:c,class:"opacity-40"}),onclick:t=>{t.stopPropagation(),r(!r())},...i}),t.if(r,(()=>t.html("div",{class:"absolute left-0 mt-2 p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box z-[100] w-80 select-none",onclick:t=>t.stopPropagation()},[t.html("div",{class:"flex justify-between items-center mb-4 gap-1"},[t.html("div",{class:"flex gap-0.5"},[t.html("button",{type:"button",class:"btn btn-ghost btn-xs px-1",onclick:()=>B(-1)},t.html("img",{src:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABlSURBVDiN3ZLBDUBAEEUfmtCchA5woUMlOO1FCQrAwbqwf8eFhHd7mfzJn2Tg82TGvABywAmPUgOLD4XcDK9AJ/y5cOlrNsIvpCdPDL/FUbkX/t6Slv3+SjgQf6QBmIAZGAP+FzZJViOd89x8pAAAAABJRU5ErkJggg==",class:"opacity-40"})),t.html("button",{type:"button",class:"btn btn-ghost btn-xs px-1",onclick:()=>v(-1)},t.html("img",{src:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABfSURBVDiNY2AY8oCZSHWxDAwMEgwMDHfJsaSAgYHhH9QQsjT/Z2BgKKe75gQGiLMLCSlkwiHOSI6t6ADmhYoBN6SIARIeidgkiUlIxxkYGB4xMDB8YmBguE6JSwYpAACvLRHTKwPjZgAAAABJRU5ErkJggg==",class:"opacity-40"}))]),t.html("span",{class:"font-bold uppercase flex-1 text-center"},[()=>d().toLocaleString("es-ES",{month:"short",year:"numeric"})]),t.html("div",{class:"flex gap-0.5"},[t.html("button",{type:"button",class:"btn btn-ghost btn-xs px-1",onclick:()=>v(1)},t.html("img",{src:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABNSURBVDiN3dAxCoAwFATRh3fU2oAHiDbi5Y1F2jT+gKLbzyy7/DYjUo8g4cTWI8koOF6XrOqc5ifDDVGJthfsj8OLujtHYJgwR+GP5QKMxA9/SolDQgAAAABJRU5ErkJggg==",class:"opacity-40"})),t.html("button",{type:"button",class:"btn btn-ghost btn-xs px-1",onclick:()=>B(1)},t.html("img",{src:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABmSURBVDiN3dGxCoAgEMbxfz1dL1BTREJzmUv08trgDYcg6VCD3/YD7zvkoLmMgFEegLmmwAAecOJVvNeUWCAAt7IHjt9LThkyiRf9qC8oCom70u0BuDL+bngj/tNm/JqJePucW8wDvGYdzT0nMUkAAAAASUVORK5CYII=",class:"opacity-40"}))])]),t.html("div",{class:"grid grid-cols-7 gap-1",onmouseleave:()=>m(null)},[...["L","M","X","J","V","S","D"].map((e=>t.html("div",{class:"text-[10px] opacity-40 font-bold text-center"},e))),()=>{const e=d(),l=e.getFullYear(),s=e.getMonth(),n=new Date(l,s,1).getDay(),o=0===n?6:n-1,c=new Date(l,s+1,0).getDate(),i=[];for(let e=0;e{const t=A(a),e=m(),l="string"==typeof t?t===o:t?.start===o,s=t?.end===o;let n=!1;if(h()&&t?.start){const l=t.start;!t.end&&e?n=o>l&&o<=e||o=e:t.end&&(n=o>l&&o{h()&&m(o)},onclick:()=>p(n)},[e.toString()]))}return i}])]))),t.if(r,(()=>t.html("div",{class:"fixed inset-0 z-[90]",onclick:()=>r(!1)})))])},l.Colorpicker=e=>{const{$value:l,label:a,...s}=e,n=t(!1),o=["#000","#1A1A1A","#333","#4D4D4D","#666","#808080","#B3B3B3","#FFF","#450a0a","#7f1d1d","#991b1b","#b91c1c","#dc2626","#ef4444","#f87171","#fca5a5","#431407","#7c2d12","#9a3412","#c2410c","#ea580c","#f97316","#fb923c","#ffedd5","#713f12","#a16207","#ca8a04","#eab308","#facc15","#fde047","#fef08a","#fff9c4","#064e3b","#065f46","#059669","#10b981","#34d399","#4ade80","#84cc16","#d9f99d","#082f49","#075985","#0284c7","#0ea5e9","#38bdf8","#7dd3fc","#22d3ee","#cffafe","#1e1b4b","#312e81","#4338ca","#4f46e5","#6366f1","#818cf8","#a5b4fc","#e0e7ff","#2e1065","#4c1d95","#6d28d9","#7c3aed","#8b5cf6","#a855f7","#d946ef","#fae8ff"],c=()=>A(l)||"#000000";return t.html("div",{class:"relative w-fit"},[t.html("button",{type:"button",class:"btn px-3 bg-base-100 border-base-300 hover:border-primary/50 flex items-center gap-2 shadow-sm font-normal normal-case",onclick:t=>{t.stopPropagation(),n(!n())},...s},[t.html("div",{class:"size-5 rounded-sm shadow-inner border border-black/10 shrink-0",style:()=>`background-color: ${c()}`}),a?t.html("span",{class:"opacity-80"},a):null]),t.if(n,(()=>t.html("div",{class:"absolute left-0 mt-2 p-3 bg-base-100 border border-base-300 shadow-2xl rounded-box z-[110] w-64 select-none",onclick:t=>t.stopPropagation()},[t.html("div",{class:"grid grid-cols-8 gap-1"},o.map((e=>t.html("button",{type:"button",style:`background-color: ${e}`,class:()=>"size-6 rounded-sm cursor-pointer transition-all hover:scale-125 hover:z-10 active:scale-95 outline-none border border-black/5 \n "+(c().toLowerCase()===e.toLowerCase()?"ring-2 ring-offset-1 ring-primary z-10 scale-110":""),onclick:()=>{l(e),n(!1)}}))))]))),t.if(n,(()=>t.html("div",{class:"fixed inset-0 z-[100]",onclick:()=>n(!1)})))])},l.CheckBox=e=>{const{$value:l,tooltip:a,toggle:s,...n}=e,o=t.html("input",{...n,type:"checkbox",class:()=>A(s)?"toggle":"checkbox",$checked:l,onchange:t=>l?.(t.target.checked)}),c=t.html("label",{class:"label cursor-pointer justify-start gap-3"},[o,e.label?t.html("span",{class:"label-text"},e.label):null]);return a?t.html("div",{class:"tooltip","data-tip":a},c):c},l.Radio=e=>{const{label:l,tooltip:a,$value:s,value:n,...c}=e,i=t.html("input",{...c,type:"radio",class:o("radio",e.$class||e.class),$checked:()=>A(s)===n,$disabled:()=>A(e.$disabled)||A(e.disabled),onclick:()=>"function"==typeof s&&s(n)});if(!l&&!a)return i;const r=t.html("label",{class:"label cursor-pointer justify-start gap-3"},[i,l?t.html("span",{class:"label-text"},l):null]);return a?t.html("div",{class:"tooltip","data-tip":a},r):r},l.Range=e=>{const{label:l,tooltip:a,$value:s,...n}=e,c=t.html("input",{...n,type:"range",class:o("range",e.$class||e.class),$value:s,$disabled:()=>A(e.$disabled)||A(e.disabled),oninput:t=>"function"==typeof s&&s(t.target.value)});if(!l&&!a)return c;const i=t.html("div",{class:"flex flex-col gap-2"},[l?t.html("span",{class:"label-text"},l):null,c]);return a?t.html("div",{class:"tooltip","data-tip":a},i):i},l.Modal=(e,a)=>{const{title:s,buttons:A,$open:o,...c}=e,i=()=>o(!1);return t.if(o,(()=>t.html("dialog",{...c,class:"modal modal-open"},[t.html("div",{class:"modal-box"},[s?t.html("h3",{class:"text-lg font-bold mb-4"},s):null,"function"==typeof a?a():a,t.html("div",{class:"modal-action flex gap-2"},[...(Array.isArray(A)?A:[A]).filter(Boolean),l.Button({onclick:i},n("close")())])]),t.html("form",{method:"dialog",class:"modal-backdrop",onclick:t=>(t.preventDefault(),i())},[t.html("button",{},"close")])])))},l.Grid=e=>{const{data:l,options:a,class:s}=e;let n=null;const o=t.html("div",{style:"height: 100%; width: 100%;",class:s}),c=new MutationObserver((()=>{n&&n.setGridOption("theme",getTheme(isDark()))}));c.observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme"]}),o._cleanups.add((()=>c.disconnect()));const i=t.watch((()=>{const t=isDark(),e=getTheme(t),s=A(l)||[];n?n.setGridOption("theme",e):n=createGrid(o,{...A(a)||{},theme:e,rowData:s})}));o._cleanups.add(i);const r=t.watch((()=>{const t=A(l);n&&Array.isArray(t)&&n.setGridOption("rowData",t)}));return o._cleanups.add(r),o._cleanups.add((()=>{n&&(n.destroy(),n=null)})),o},l.Dropdown=(e,l)=>{const{label:a,icon:s,...n}=e;return t.html("div",{...n,class:()=>`dropdown ${A(e.$class)||e.class||""}`},[t.html("div",{tabindex:0,role:"button",class:"btn m-1 flex items-center gap-2"},[s?"function"==typeof s?s():s:null,a?"function"==typeof a?a():a:null]),t.html("ul",{tabindex:0,class:"dropdown-content z-[50] menu p-2 shadow bg-base-100 rounded-box min-w-max border border-base-300"},["function"==typeof l?l():l])])},l.Accordion=(e,l)=>{const{title:a,name:s,$open:n,open:c,...i}=e;return t.html("div",{...i,class:o("collapse collapse-arrow bg-base-200 mb-2",e.$class||e.class)},[t.html("input",{type:s?"radio":"checkbox",name:s,$checked:()=>A(n)||A(c),onchange:t=>"function"==typeof n&&n(t.target.checked)}),t.html("div",{class:"collapse-title text-xl font-medium"},a),t.html("div",{class:"collapse-content"},l)])},l.Tabs=e=>{const{items:l,...a}=e,s="function"==typeof l?l:()=>l||[];return t.html("div",{...a,class:"flex flex-col gap-4 w-full"},[t.html("div",{role:"tablist",class:o("tabs tabs-box",e.$class||e.class)},t.for(s,(e=>t.html("a",{role:"tab",class:()=>o("tab",A(e.active)&&"tab-active",A(e.disabled),e.tip),"data-tip":e.tip,onclick:t=>!A(e.disabled)&&e.onclick?.(t)},e.label)),(t=>t.label))),()=>{const e=s().find((t=>A(t.active)));if(!e)return null;const l=A(e.content);return t.html("div",{class:"p-4"},["function"==typeof l?l():l])}])},l.Badge=(e,l)=>t.html("span",{...e,class:o("badge",e.$class||e.class)},l),l.Tooltip=(e,l)=>t.html("div",{...e,class:o("tooltip",e.$class||e.class),"data-tip":e.tip},l),l.Navbar=(e,l)=>t.html("div",{...e,class:o("navbar bg-base-100 shadow-sm px-4",e.$class||e.class)},l),l.Menu=e=>{const l=e=>t.for((()=>e||[]),(e=>t.html("li",{},[e.children?t.html("details",{open:e.open},[t.html("summary",{},[e.icon&&t.html("span",{class:"mr-2"},e.icon),e.label]),t.html("ul",{},l(e.children))]):t.html("a",{class:()=>A(e.active)?"active":"",onclick:e.onclick},[e.icon&&t.html("span",{class:"mr-2"},e.icon),e.label])])),((t,e)=>t.label||e));return t.html("ul",{...e,class:o("menu bg-base-200 rounded-box",e.$class||e.class)},l(e.items))},l.Drawer=e=>t.html("div",{class:o("drawer",e.$class||e.class)},[t.html("input",{id:e.id,type:"checkbox",class:"drawer-toggle",$checked:e.$open}),t.html("div",{class:"drawer-content"},e.content),t.html("div",{class:"drawer-side"},[t.html("label",{for:e.id,class:"drawer-overlay",onclick:()=>e.$open?.(!1)}),t.html("div",{class:"min-h-full bg-base-200 w-80"},e.side)])]),l.Fieldset=(e,l)=>t.html("fieldset",{...e,class:o("fieldset bg-base-200 border border-base-300 p-4 rounded-lg",e.$class||e.class)},[()=>{const l=A(e.legend);return l?t.html("legend",{class:"fieldset-legend font-bold"},[l]):null},l]),l.List=e=>{const{items:l,header:a,render:s,keyFn:n,class:c}=e;return t.html("ul",{class:o("list bg-base-100 rounded-box shadow-md",c)},[t.if(a,(()=>t.html("li",{class:"p-4 pb-2 text-xs opacity-60 tracking-wide"},[A(a)]))),t.for(l,((e,l)=>t.html("li",{class:"list-row"},[s(e,l)])),n)])},l.Stack=(e,l)=>t.html("div",{...e,class:o("stack",e.$class||e.class)},l),l.Stat=e=>t.html("div",{...e,class:o("stat",e.$class||e.class)},[e.icon&&t.html("div",{class:"stat-figure text-secondary"},e.icon),e.label&&t.html("div",{class:"stat-title"},e.label),t.html("div",{class:"stat-value"},(()=>A(e.$value)??e.value)),e.desc&&t.html("div",{class:"stat-desc"},e.desc)]),l.Swap=e=>t.html("label",{class:o("swap",e.$class||e.class)},[t.html("input",{type:"checkbox",$checked:e.$value,onchange:t=>e.$value?.(t.target.checked)}),t.html("div",{class:"swap-on"},e.on),t.html("div",{class:"swap-off"},e.off)]),l.Indicator=(e,l)=>t.html("div",{class:o("indicator",e.$class||e.class)},[l,t.html("span",{class:o("indicator-item badge",e.badgeClass)},e.badge)]),l.Rating=e=>{const{$value:l,count:a=5,name:s=`rating-${Math.random().toString(36).slice(2,7)}`,mask:n="mask-star",readonly:o=!1,...c}=e;return t.html("div",{...c,class:()=>`rating ${A(o)?"pointer-events-none":""} ${e.class||""}`},Array.from({length:A(a)},((e,a)=>{const c=a+1;return t.html("input",{type:"radio",name:s,class:`mask ${n}`,"aria-label":`${c} star`,checked:()=>Math.round(A(l))===c,onchange:()=>{A(o)||"function"!=typeof l||l(c)}})})))},l.Alert=(e,l)=>{const{type:a="info",soft:s=!0,...n}=e,o={info:i,success:r,warning:m,error:d},c=l||e.message;return t.html("div",{...n,role:"alert",class:()=>`alert ${(()=>{const t=A(a);return{info:"alert-info",success:"alert-success",warning:"alert-warning",error:"alert-error"}[t]||t})()} ${A(s)?"alert-soft":""} ${e.class||""}`},[t.html("img",{src:o[A(a)]||o.info,class:"w-4 h-4 object-contain",alt:A(a)}),t.html("div",{class:"flex-1"},[t.html("span",{},["function"==typeof c?c():c])]),e.actions?t.html("div",{class:"flex-none"},["function"==typeof e.actions?e.actions():e.actions]):null])},l.Timeline=e=>{const{items:l=[],vertical:a=!0,compact:s=!1,...n}=e,o={info:i,success:r,warning:m,error:d};return t.html("ul",{...n,class:()=>`timeline ${A(a)?"timeline-vertical":"timeline-horizontal"} ${A(s)?"timeline-compact":""} ${e.class||""}`},[t.for(l,((e,a)=>{const s=0===a,n=a===A(l).length-1,c=e.type||"success",i=t=>"function"==typeof t?t():t;return t.html("li",{class:"flex-1"},[s?null:t.html("hr",{class:e.completed?"bg-primary":""}),t.html("div",{class:"timeline-start"},[i(e.title)]),t.html("div",{class:"timeline-middle"},[t.html("img",{src:o[c]||e.icon||o.success,class:"w-4 h-4 object-contain mx-1",alt:c})]),t.html("div",{class:"timeline-end timeline-box shadow-sm"},[i(e.detail)]),n?null:t.html("hr",{class:e.completed?"bg-primary":""})])}),((t,e)=>t.id||e))])},l.Fab=e=>{const{icon:l,label:a,actions:s=[],position:n="bottom-6 right-6",...o}=e;return t.html("div",{...o,class:()=>`fab fixed ${A(n)} flex flex-col-reverse items-end gap-3 z-[100] ${e.class||""}`},[t.html("div",{tabindex:0,role:"button",class:"btn btn-lg btn-circle btn-primary shadow-2xl"},[l?"function"==typeof l?l():l:null,!l&&a?a:null]),...A(s).map((e=>t.html("div",{class:"flex items-center gap-3 transition-all duration-300"},[e.label?t.html("span",{class:"badge badge-ghost shadow-sm whitespace-nowrap"},e.label):null,t.html("button",{type:"button",class:`btn btn-circle shadow-lg ${e.class||""}`,onclick:t=>{t.stopPropagation(),e.onclick?.(t)}},[e.icon?"function"==typeof e.icon?e.icon():e.icon:e.text||""])])))])},l.Toast=(e,a="alert-success",s=3500)=>{let n=document.getElementById("sigpro-toast-container");n||(n=t.html("div",{id:"sigpro-toast-container",class:"fixed top-0 right-0 z-[9999] p-4 flex flex-col gap-2 pointer-events-none"}),document.body.appendChild(n));const A=t.html("div",{style:"display: contents"});let o;n.appendChild(A);const c=()=>{clearTimeout(o);const t=A.firstElementChild;t&&!t.classList.contains("opacity-0")?(t.classList.add("translate-x-full","opacity-0"),setTimeout((()=>{i.destroy(),A.remove(),n.hasChildNodes()||n.remove()}),300)):(i.destroy(),A.remove())},i=t.mount((()=>{const s=t.html("div",{class:`alert alert-soft ${a} shadow-lg transition-all duration-300 translate-x-10 opacity-0 pointer-events-auto`},[t.html("span","function"==typeof e?e:()=>e),l.Button({class:"btn-xs btn-circle btn-ghost",onclick:c},"✕")]);return requestAnimationFrame((()=>s.classList.remove("translate-x-10","opacity-0"))),s}),A);return s>0&&(o=setTimeout(c,s)),c},l.Loading=e=>t.if(e.$show,(()=>t.html("div",{class:"fixed inset-0 z-[100] flex items-center justify-center backdrop-blur-sm bg-base-100/30"},[t.html("span",{class:"loading loading-spinner loading-lg text-primary"})]))),l.tt=n,Object.keys(l).forEach((e=>{window[e]=l[e],t[e]=l[e]})),l}; \ No newline at end of file diff --git a/docs/404.html b/docs/404.html index fe43e03..5c7ad96 100644 --- a/docs/404.html +++ b/docs/404.html @@ -17,7 +17,7 @@
- + \ No newline at end of file diff --git a/docs/api/effect.html b/docs/api/effect.html deleted file mode 100644 index 5f90ccf..0000000 --- a/docs/api/effect.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - ⚡ Side Effects: $.effect( ) | SigPro - - - - - - - - - - - - - - - -
Skip to content

⚡ Side Effects: $.effect( )

The $.effect function allows you to run a piece of code whenever the signals it depends on are updated. It automatically tracks any signal called within its body.

🛠 Function Signature

typescript
$.effect(callback: Function): StopFunction
ParameterTypeRequiredDescription
callbackFunctionYesThe code to run. It will execute immediately and then re-run on dependency changes.

Returns: A StopFunction that, when called, cancels the effect and prevents further executions.


📖 Usage Patterns

1. Basic Tracking

Any signal you "touch" inside the effect becomes a dependency.

javascript
const count = $(0);
-
-$.effect(() => {
-  // This runs every time 'count' changes
-  console.log(`The count is now: ${count()}`);
-});
-
-count(1); // Console: "The count is now: 1"

2. Manual Cleanup

If your effect creates something that needs to be destroyed (like a timer or a global event listener), you can return a cleanup function.

javascript
$.effect(() => {
-  const timer = setInterval(() => console.log("Tick"), 1000);
-
-  // SigPro will run this BEFORE the next effect execution 
-  // or when the effect is stopped.
-  return () => clearInterval(timer);
-});

3. Nesting & Automatic Cleanup

If you create a signal or another effect inside an effect, SigPro tracks them as "children". When the parent effect re-runs or stops, all children are automatically cleaned up to prevent memory leaks.

javascript
$.effect(() => {
-  if (isLoggedIn()) {
-    // This sub-effect is only active while 'isLoggedIn' is true
-    $.effect(() => {
-      console.log("Fetching user data...");
-    });
-  }
-});

🛑 Stopping an Effect

You can stop an effect manually by calling the function it returns. This is useful for one-time operations or complex logic.

javascript
const stop = $.effect(() => {
-  console.log(count());
-});
-
-// Later...
-stop(); // The effect is destroyed and will never run again.

💡 Pro Tip: Batching

SigPro uses a Microtask Queue to handle updates. If you update multiple signals at once, the effect will only run once at the end of the current task.

javascript
const a = $(0);
-const b = $(0);
-
-$.effect(() => console.log(a(), b()));
-
-// This triggers only ONE re-run, not two.
-a(1);
-b(2);
- - - - \ No newline at end of file diff --git a/docs/api/html.html b/docs/api/html.html index aab3d97..f77887d 100644 --- a/docs/api/html.html +++ b/docs/api/html.html @@ -13,13 +13,13 @@ - + -
Skip to content

🏗️ The DOM Factory: $.html( )

$.html is the internal engine that creates, attributes, and attaches reactivity to DOM elements. It is the foundation for all Tag Constructors in SigPro.

🛠 Function Signature

typescript
$.html(tagName: string, props?: Object, children?: any[] | any): HTMLElement
ParameterTypeRequiredDescription
tagNamestringYesValid HTML tag name (e.g., "div", "button").
propsObjectNoHTML attributes, event listeners, and reactive bindings.
childrenanyNoNested elements, text strings, or reactive functions.

📖 Key Features

1. Attribute Handling

SigPro intelligently decides how to apply each property:

  • Standard Props: Applied via setAttribute or direct property assignment.
  • Boolean Props: Uses toggleAttribute (e.g., checked, disabled, hidden).
  • Class Names: Supports class or className interchangeably.

2. Event Listeners & Modifiers

Events are defined by the on prefix. SigPro supports Dot Notation for common event operations:

javascript
$.html("button", {
+    
Skip to content

🏗️ The DOM Factory: $.html( )

$.html is the internal engine that creates, attributes, and attaches reactivity to DOM elements. In V2, it uses $.watch to maintain a live, high-performance link between your Signals and the Document Object Model.

🛠 Function Signature

typescript
$.html(tagName: string, props?: Object, children?: any[] | any): HTMLElement
ParameterTypeRequiredDescription
tagNamestringYesValid HTML tag name (e.g., "div", "button").
propsObjectNoHTML attributes, event listeners, and reactive bindings.
childrenanyNoNested elements, text strings, or reactive functions.

📖 Key Features

1. Attribute Handling

SigPro intelligently decides how to apply each property:

  • Standard Props: Applied via setAttribute or direct property assignment.
  • Boolean Props: Uses toggleAttribute (e.g., checked, disabled, hidden).
  • Class Names: Supports class or className interchangeably.

2. Event Listeners & Modifiers

Events are defined by the on prefix. SigPro supports Dot Notation for common event operations:

javascript
$.html("button", {
   // e.preventDefault() is called automatically
   "onsubmit.prevent": (e) => save(e), 
   
@@ -28,22 +28,22 @@
   
   // { once: true } listener option
   "onclick.once": () => console.log("Runs only once")
-}, "Click Me");

3. Reactive Attributes

If an attribute value is a function (like a Signal), $.html creates an internal $.effect to keep the DOM in sync with the state.

javascript
$.html("div", {
+}, "Click Me");

3. Reactive Attributes

If an attribute value is a function (like a Signal), $.html creates an internal $.watch to keep the DOM in sync with the state.

javascript
$.html("div", {
   // Updates the class whenever 'theme()' changes
   class: () => theme() === "dark" ? "bg-black" : "bg-white"
-});

4. Reactive Children

Children can be static or dynamic. When a child is a function, SigPro creates a reactive boundary for that specific part of the DOM.

javascript
$.html("div", {}, [
+});

4. Reactive Children

Children can be static or dynamic. When a child is a function, SigPro creates a reactive boundary using $.watch for that specific part of the DOM.

javascript
$.html("div", {}, [
   H1("Static Title"),
   // Only this text node re-renders when 'count' changes
   () => `Current count: ${count()}`
 ]);

🔄 Two-Way Binding Operator ($)

When a property starts with $, $.html enables bidirectional synchronization. This is primarily used for form inputs.

javascript
$.html("input", {
   type: "text",
   $value: username // Syncs input value <-> signal
-});

🧹 Automatic Cleanup

Every element created with $.html gets a hidden ._cleanups property (a Set).

  • When SigPro removes an element via $.view or $.router, it automatically executes all functions stored in this Set (stopping effects, removing listeners, etc.).

💡 Tag Constructors (The Shortcuts)

Instead of writing $.html("div", ...) every time, SigPro provides PascalCase global functions:

javascript
// This:
+});

🧹 Memory Management (Internal)

Every element created with $.html is "self-aware" regarding its reactive dependencies.

  • ._cleanups: A hidden Set attached to the element that stores all stop() functions from its internal $.watch calls and event listeners.
  • Lifecycle: When an element is removed by a Controller ($.If, $.For, or $.router), SigPro performs a recursive "sweep" to execute these cleanups, ensuring zero memory leaks.

💡 Tag Constructors (The Shortcuts)

Instead of writing $.html("div", ...) every time, SigPro provides PascalCase global functions:

javascript
// This:
 Div({ class: "wrapper" }, [ Span("Hello") ])
 
 // Is exactly equivalent to:
-$.html("div", { class: "wrapper" }, [ $.html("span", {}, "Hello") ])
- +$.html("div", { class: "wrapper" }, [ $.html("span", {}, "Hello") ])
+ \ No newline at end of file diff --git a/docs/api/ignore.html b/docs/api/ignore.html deleted file mode 100644 index f8cfb47..0000000 --- a/docs/api/ignore.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - 🛑 Untracking: $.ignore( ) | SigPro - - - - - - - - - - - - - - - -
Skip to content

🛑 Untracking: $.ignore( )

The $.ignore function allows you to read a signal's value inside an effect or a computed signal without creating a dependency.

🛠 Function Signature

typescript
$.ignore(callback: Function): any
ParameterTypeRequiredDescription
callbackFunctionYesA function where signals can be read "silently".

Returns: Whatever the callback function returns.


📖 Usage Patterns

1. Preventing Dependency Tracking

Normally, reading a signal inside $.effect makes the effect re-run when that signal changes. $.ignore breaks this link.

javascript
const count = $(0);
-const logLabel = $("System Log");
-
-$.effect(() => {
-  // This effect tracks 'count'...
-  const currentCount = count();
-  
-  // ...but NOT 'logLabel'. 
-  // Changing 'logLabel' will NOT re-run this effect.
-  const label = $.ignore(() => logLabel());
-  
-  console.log(`${label}: ${currentCount}`);
-});
-
-count(1);     // Console: "System Log: 1" (Triggers re-run)
-logLabel("UI"); // Nothing happens in console (Ignored)

2. Reading State in Event Handlers

Inside complex UI logic, you might want to take a "snapshot" of a signal without triggering a reactive chain.

javascript
const handleClick = () => {
-  // Accessing state without letting the caller know we touched it
-  const data = $.ignore(() => mySignal());
-  process(data);
-};

3. Avoiding Infinite Loops

If you need to write to a signal based on its own value inside an effect (and you aren't using the functional updater), $.ignore prevents the effect from triggering itself.

javascript
$.effect(() => {
-  const value = someSignal();
-  
-  if (value > 100) {
-    // We update the signal, but we ignore the read to avoid a loop
-    $.ignore(() => someSignal(0));
-  }
-});

💡 Why use it?

  • Performance: Prevents expensive effects from running when non-essential data changes.
  • Logic Control: Allows "sampling" a signal at a specific point in time.
  • Safety: Essential for complex state orchestrations where circular dependencies might occur.
- - - - \ No newline at end of file diff --git a/docs/api/mount.html b/docs/api/mount.html index fdbde8c..073ad24 100644 --- a/docs/api/mount.html +++ b/docs/api/mount.html @@ -13,40 +13,29 @@ - + -
Skip to content

🔌 Application Mounter: $.mount( )

The $.mount function is the entry point of your reactive world. It bridges the gap between your SigPro logic and the browser's Real DOM by injecting a component into the document.

1. Function Signature

typescript
$.mount(node: Function | HTMLElement, target?: string | HTMLElement): RuntimeObject
ParameterTypeDefaultDescription
nodeFunction or NodeRequiredThe component function or DOM element to render.
targetstring or Nodedocument.bodyCSS selector or DOM element where the app will live.

Returns: A Runtime object containing the container and a destroy() method.


2. Common Usage Scenarios

A. The "Clean Slate" (SPA Entry)

In a modern Single Page Application, you typically want SigPro to manage the entire view. By default, if no target is provided, it mounts to document.body.

javascript
import { $ } from './sigpro.js';
+    
Skip to content

🔌 Application Mounter: $.mount( )

The $.mount function is the entry point of your reactive world. It bridges the gap between your SigPro logic and the browser's Real DOM by injecting a component into the document and initializing its reactive lifecycle.

🛠️ Function Signature

typescript
$.mount(node: Function | HTMLElement, target?: string | HTMLElement): RuntimeObject
ParameterTypeDefaultDescription
nodeFunction or NodeRequiredThe component function or DOM element to render.
targetstring or Nodedocument.bodyCSS selector or DOM element where the app will live.

Returns: A Runtime object containing the container and a destroy() method to wipe all reactivity and DOM nodes.


📖 Common Usage Scenarios

1. The SPA Entry Point

In a Single Page Application, you typically mount your main component to the body or a root div. SigPro manages the entire view from that point.

javascript
import { $ } from './sigpro.js';
 import App from './App.js';
 
-// Mounts your main App component directly to the body
-$.mount(App);

B. Targeting a Specific Container

If your HTML has a predefined structure, you can tell SigPro exactly where to render by passing a CSS selector or a direct reference.

html
<div id="sidebar"></div>
-<main id="app-root"></main>
javascript
// Mount using a CSS selector
-$.mount(MyComponent, '#app-root');
-
-// Mount using a direct DOM reference
-const sidebar = document.querySelector('#sidebar');
-$.mount(SidebarComponent, sidebar);

C. Creating "Reactive Islands"

SigPro is excellent for "sprinkling" reactivity onto legacy or static pages. You can inject small reactive widgets into any part of an existing HTML layout.

javascript
// A small reactive widget
-const CounterWidget = () => {
+// Mounts your main App component
+$.mount(App, '#app-root');

2. Reactive "Islands"

SigPro is perfect for adding reactivity to static pages. You can mount small widgets into specific parts of an existing HTML layout.

javascript
const Counter = () => {
   const count = $(0);
   return Button({ onclick: () => count(c => c + 1) }, [
     "Clicks: ", count
   ]);
 };
 
-// Mount it into a specific div in your static HTML
-$.mount(CounterWidget, '#counter-container');

3. How it Works (Lifecycle)

When $.mount is executed, it performs these critical steps:

  1. Resolution & Wrapping: If you pass a Function, SigPro wraps it in a $.view(). This starts tracking all internal signals and effects.
  2. Target Clearance: It uses target.replaceChildren(). This efficiently wipes any existing HTML or "zombie" nodes inside the target before mounting.
  3. Injection: The component's container is appended to the target.
  4. Memory Management: It stores the Runtime instance associated with that DOM element. If you call $.mount again on the same target, SigPro automatically destroys the previous app to prevent memory leaks.

4. Global vs. Local Scope

The "Framework" Way (Global)

By importing your core in your entry file, SigPro automatically initializes global Tag Constructors (Div, Span, H1, etc.). This allows for a clean, declarative DX across your entire project.

javascript
// main.js
-import './sigpro.js'; 
+// Mount only the counter into a specific sidebar div
+$.mount(Counter, '#sidebar-widget');

🔄 How it Works (Lifecycle & Saneamiento)

When $.mount is executed, it performs these critical steps to ensure a leak-free environment:

  1. Duplicate Detection: If you call $.mount on a target that already has a SigPro instance, it automatically calls .destroy() on the previous instance. This prevents "Zombie Effects" from stacking in memory.
  2. Internal Scoping: It executes the component function inside an internal Reactive Owner. This captures every $.watch and event listener created during the render.
  3. Target Injection: It clears the target using replaceChildren() and appends the new component.
  4. Runtime Creation: It returns a control object:
    • container: The actual DOM element created.
    • destroy(): The "kill switch" that runs all cleanups, stops all watchers, and removes the element from the DOM.

🛑 Manual Unmounting

While SigPro handles most cleanups automatically (via $.If, $.For, and $.router), you can manually destroy any mounted instance. This is vital for imperatively managed UI like Toasts or Modals.

javascript
const instance = $.mount(MyToast, '#toast-container');
 
-// Now any file can simply do:
-$.mount(() => H1("Global SigPro App"));

The "Library" Way (Local)

If you prefer to avoid global variables, you can use the low-level $.html factory to create elements locally.

javascript
import { $ } from './sigpro.js';
-
-const myNode = $.html('div', { class: 'widget' }, 'Local Instance');
-$.mount(myNode, '#widget-target');

5. Summary Cheat Sheet

GoalCode Pattern
Mount to body$.mount(App)
Mount to ID$.mount(App, '#root')
Mount to Element$.mount(App, myElement)
Mount raw Node$.mount(Div("Hello"), '#id')
Unmount/Destroyconst app = $.mount(App); app.destroy();
- +// Later, to remove the toast and kill its reactivity: +instance.destroy();

💡 Summary Cheat Sheet

GoalCode Pattern
Mount to body$.mount(App)
Mount to CSS Selector$.mount(App, '#root')
Mount to DOM Node$.mount(App, myElement)
Clean & Re-mountCalling $.mount again on the same target
Total Saneamientoconst app = $.mount(App); app.destroy();
+ \ No newline at end of file diff --git a/docs/api/quick.html b/docs/api/quick.html index c3a715a..ac980b0 100644 --- a/docs/api/quick.html +++ b/docs/api/quick.html @@ -19,8 +19,8 @@ -
Skip to content

⚡ Quick API Reference

SigPro is a high-performance micro-framework that updates the Real DOM surgically. No Virtual DOM, no unnecessary re-renders.

🟢 Core Functions

FunctionSignatureDescription
$(val, key?)(any, string?) => SignalCreates a Signal. If key is provided, it persists in localStorage.
$(fn)(function) => ComputedCreates a Computed Signal that auto-updates when its dependencies change.
$.effect(fn)(function) => stopFnRuns a side-effect that tracks signals. Returns a function to manually stop it.
$.html(tag, props, children)(string, object, any) => HTMLElementThe low-level DOM factory powering all tag constructors.
$.router(routes)(Array) => HTMLElementInitializes the hash-based router for SPAs.
$.go(path)(string) => voidProgrammatic navigation (e.g., $.go('/home')).
$.mount(comp, target)(any, string|Node) => RuntimeMounts the application into the specified DOM element.
$.ignore(fn)(function) => anyExecutes code without tracking any signals inside it.

🏗️ Element Constructors (Tags)

SigPro provides PascalCase wrappers for all standard HTML5 tags (e.g., Div, Span, Button).

Syntax Pattern

javascript
Tag({ attributes }, [children])

Attribute & Content Handling

PatternCode ExampleBehavior
Staticclass: "text-red"Standard HTML attribute string.
Reactivedisabled: isLoadingUpdates automatically when isLoading() changes.
Two-way$value: usernameSyncs input with signal both ways (Binding Operator).
TextP({}, () => count())Updates text node whenever count changes.
Booleanhidden: isHiddenToggles the attribute based on signal truthiness.
- +
Skip to content

⚡ Quick API Reference

SigPro is a high-performance micro-framework that updates the Real DOM surgically. No Virtual DOM, no unnecessary re-renders.

🟢 Core Functions

FunctionSignatureDescription
$(val, key?)(any, string?) => SignalCreates a Signal. If key is provided, it persists in localStorage.
$(fn)(function) => ComputedCreates a Computed Signal that auto-updates when its dependencies change.
$.effect(fn)(function) => stopFnRuns a side-effect that tracks signals. Returns a function to manually stop it.
$.html(tag, props, children)(string, object, any) => HTMLElementThe low-level DOM factory powering all tag constructors.
$.router(routes)(Array) => HTMLElementInitializes the hash-based router for SPAs.
$.go(path)(string) => voidProgrammatic navigation (e.g., $.go('/home')).
$.mount(comp, target)(any, string|Node) => RuntimeMounts the application into the specified DOM element.
$.ignore(fn)(function) => anyExecutes code without tracking any signals inside it.

🏗️ Element Constructors (Tags)

SigPro provides PascalCase wrappers for all standard HTML5 tags (e.g., Div, Span, Button).

Syntax Pattern

javascript
Tag({ attributes }, [children])

Attribute & Content Handling

PatternCode ExampleBehavior
Staticclass: "text-red"Standard HTML attribute string.
Reactivedisabled: isLoadingUpdates automatically when isLoading() changes.
Two-way$value: usernameSyncs input with signal both ways (Binding Operator).
TextP({}, () => count())Updates text node whenever count changes.
Booleanhidden: isHiddenToggles the attribute based on signal truthiness.
+ \ No newline at end of file diff --git a/docs/api/router.html b/docs/api/router.html index 0a9c980..5ff613d 100644 --- a/docs/api/router.html +++ b/docs/api/router.html @@ -19,7 +19,7 @@ -
Skip to content

🚦 Routing: $.router( ) & $.go( )

SigPro includes a built-in, lightweight Hash Router to create Single Page Applications (SPA). It manages the URL state, matches components to paths, and handles the lifecycle of your pages automatically.

🛠 Router Signature

typescript
$.router(routes: Route[]): HTMLElement

Route Object

PropertyTypeDescription
pathstringThe URL fragment (e.g., "/home", "/user/:id", or "*").
componentFunctionA function that returns a Tag or a $.view.

📖 Usage Patterns

1. Defining Routes

The router returns a div element with the class .router-outlet. When the hash changes, the router destroys the previous view and mounts the new one inside this container.

javascript
const App = () => Div({ class: "app-layout" }, [
+    
Skip to content

🚦 Routing: $.router( ) & $.go( )

SigPro includes a built-in, lightweight Hash Router to create Single Page Applications (SPA). It manages the URL state, matches components to paths, and handles the lifecycle of your pages automatically.

🛠 Router Signature

typescript
$.router(routes: Route[]): HTMLElement

Route Object

PropertyTypeDescription
pathstringThe URL fragment (e.g., "/home", "/user/:id", or "*").
componentFunctionA function that returns a Tag or a $.view.

📖 Usage Patterns

1. Defining Routes

The router returns a div element with the class .router-outlet. When the hash changes, the router destroys the previous view and mounts the new one inside this container.

javascript
const App = () => Div({ class: "app-layout" }, [
   Navbar(),
   // The router outlet is placed here
   $.router([
@@ -41,7 +41,7 @@
   padding: 2rem;
   animation: fadeIn 0.2s ease-in;
 }
- + \ No newline at end of file diff --git a/docs/api/signal.html b/docs/api/signal.html index 8dcef4b..7570d9a 100644 --- a/docs/api/signal.html +++ b/docs/api/signal.html @@ -19,7 +19,7 @@ -
Skip to content

💎 The Signal Function: $( )

The $( ) function is the core constructor of SigPro. It defines how data is stored, computed, and persisted.

🛠 Function Signature

typescript
$(initialValue: any, key?: string): Signal
+    
Skip to content

💎 The Signal Function: $( )

The $( ) function is the core constructor of SigPro. It defines how data is stored, computed, and persisted.

🛠 Function Signature

typescript
$(initialValue: any, key?: string): Signal
 $(computation: Function): ComputedSignal
ParameterTypeRequiredDescription
initialValueanyYes*The starting value of your signal.
computationFunctionYes*A function that returns a value based on other signals.
keystringNoA unique name to persist the signal in localStorage.

*Either an initial value or a computation function must be provided.


📖 Usage Patterns

1. Simple State

$(value) Creates a writable signal. It returns a function that acts as both getter and setter.

javascript
const count = $(0); 
 
 count();    // Read (0)
@@ -32,8 +32,8 @@
 const total = $(() => price() * (1 + tax()));

🔄 Updating with Logic

When calling the setter, you can pass an updater function to access the current value safely.

javascript
const list = $(["A", "B"]);
 
 // Adds "C" using the previous state
-list(prev => [...prev, "C"]);
- +list(prev => [...prev, "C"]);
+ \ No newline at end of file diff --git a/docs/api/tags.html b/docs/api/tags.html index 0384194..6365825 100644 --- a/docs/api/tags.html +++ b/docs/api/tags.html @@ -13,35 +13,35 @@ - + -
Skip to content

🎨 Global Tag Helpers

In SigPro, you don't need to manually type $.html('div', ...) for every element. To keep your code declarative and readable, the engine automatically generates Global Helper Functions for all standard HTML5 tags upon initialization.

1. How it Works

SigPro iterates through a manifest of standard HTML tags and attaches a wrapper function for each one directly to the window object. This creates a specialized DSL (Domain Specific Language) that looks like a template engine but is 100% standard JavaScript.

  • Under the hood: $.html('button', { onclick: ... }, 'Click')
  • SigPro Style: Button({ onclick: ... }, 'Click')

2. The Complete Global Registry

The following functions are injected into the global scope (using PascalCase to prevent naming collisions with common JS variables) and are ready to use:

CategoryAvailable Global Functions
StructureDiv, Span, P, Section, Nav, Main, Header, Footer, Article, Aside
TypographyH1 to H6, Ul, Ol, Li, Dl, Dt, Dd, Strong, Em, Code, Pre, Small, B, U, Mark
InteractiveButton, A, Label, Br, Hr, Details, Summary, Dialog
FormsForm, Input, Select, Option, Textarea, Fieldset, Legend
TablesTable, Thead, Tbody, Tr, Th, Td, Tfoot, Caption
MediaImg, Canvas, Video, Audio, Svg, Iframe, Picture, Source

The SigPro Philosophy: Tags are not "magic strings" handled by a compiler. They are functional constructors. Every time you call Div(), you execute a pure JS function that returns a real, reactive DOM element.


3. Usage Patterns (Smart Arguments)

SigPro tag helpers are flexible. They automatically detect if you are passing attributes, children, or both.

A. Attributes + Children

The standard way to build structured UI.

javascript
Div({ class: 'container', id: 'main' }, [
+    
Skip to content

🎨 Global Tag Helpers

In SigPro V2, you don't need to manually type $.html('div', ...) for every element. To keep your code declarative and readable, the engine automatically generates Global Helper Functions for all standard HTML5 tags upon initialization.

1. How it Works

SigPro iterates through a manifest of standard HTML tags and attaches a wrapper function for each one directly to the window object. This creates a specialized DSL (Domain Specific Language) that looks like a template engine but is 100% standard JavaScript.

  • Under the hood: $.html('button', { onclick: ... }, 'Click')
  • SigPro Style: Button({ onclick: ... }, 'Click')

2. The Complete Global Registry

The following functions are injected into the global scope (using PascalCase to prevent naming collisions with common JS variables) and are ready to use:

CategoryAvailable Global Functions
StructureDiv, Span, P, Section, Nav, Main, Header, Footer, Article, Aside
TypographyH1 to H6, Ul, Ol, Li, Dl, Dt, Dd, Strong, Em, Code, Pre, Small, B, U, Mark
InteractiveButton, A, Label, Br, Hr, Details, Summary, Dialog
FormsForm, Input, Select, Option, Textarea, Fieldset, Legend
TablesTable, Thead, Tbody, Tr, Th, Td, Tfoot, Caption
MediaImg, Canvas, Video, Audio, Svg, Iframe, Picture, Source

The SigPro Philosophy: Tags are not "magic strings" handled by a compiler. They are functional constructors. Every time you call Div(), you execute a pure JS function that returns a real, reactive DOM element.


3. Usage Patterns (Smart Arguments)

SigPro tag helpers are flexible. They automatically detect if you are passing attributes, children, or both.

A. Attributes + Children

The standard way to build structured UI.

javascript
Div({ class: 'container', id: 'main' }, [
   H1("Welcome to SigPro"),
   P("The zero-VDOM framework.")
 ]);

B. Children Only (The "Skipper")

If you don't need attributes, you can skip the object and pass the content (string, array, or function) directly as the first argument.

javascript
Section([
   H2("Clean Syntax"),
   Button("I have no props!")
 ]);

C. Primitive Content

For simple tags, just pass a string or a number.

javascript
H1("Hello World"); 
-Span(42);

4. Reactive Power Examples

These helpers are natively wired into SigPro's reactivity. No manual useEffect or watch calls are needed.

Reactive Attributes

Simply pass a Signal (function) to any attribute. SigPro handles the rest.

javascript
const theme = $("light");
+Span(42);

4. Reactive Power V2

These helpers are natively wired into SigPro's $.watch engine. No manual effect management is needed; the lifecycle is tied to the DOM node.

Reactive Attributes

Simply pass a Signal (function) to any attribute. SigPro creates an internal $.watch to keep the DOM in sync.

javascript
const theme = $("light");
 
 Div({ 
-  // Updates 'class' automatically when theme() changes
+  // Updates 'class' automatically via internal $.watch
   class: () => `app-box ${theme()}` 
-}, "Themeable Box");

The Binding Operator ($)

Use the $ prefix for Two-Way Binding on inputs.

javascript
const search = $("");
+}, "Themeable Box");

The Binding Operator ($)

Use the $ prefix for Two-Way Binding on inputs. This bridges the Signal and the Input element bi-directionally.

javascript
const search = $("");
 
 Input({ 
   type: "text", 
   placeholder: "Search...",
   $value: search // UI updates Signal AND Signal updates UI
-});

Dynamic Lists

Combine tags with ui.For for high-performance list rendering.

javascript
const items = $(["Apple", "Banana", "Cherry"]);
+});

Dynamic Flow & Saneamiento

Combine tags with Core controllers for high-performance rendering. SigPro automatically cleans up the $.watch instances when nodes are removed.

javascript
const items = $(["Apple", "Banana", "Cherry"]);
 
 Ul({ class: "list-disc" }, [
-  ui.For(items, (item) => Li(item), (item) => item)
-]);

DANGER

⚠️ Important: Naming Conventions

Since SigPro injects these helpers into the global window object, follow these rules to avoid bugs:

  1. Avoid Shadowing: Don't name your local variables like the tags (e.g., const Div = ...). This will "hide" the SigPro helper.
  2. Custom Components: Always use PascalCase, UPPERCASE, or Underscore prefixes for your own component functions (e.g., UserCard, INPUT, or _Input) to distinguish them from the built-in Tag Helpers and avoid naming collisions.

6. Logic to UI Comparison

Here is how a dynamic User Status component translates from SigPro logic to the final DOM structure.

javascript
// SigPro Component
+  $.For(items, (item) => Li(item))
+]);

DANGER

⚠️ Important: Naming Conventions

Since SigPro injects these helpers into the global window object, follow these rules to avoid bugs:

  1. Avoid Shadowing: Don't name your local variables like the tags (e.g., const Div = ...). This will "hide" the SigPro helper.
  2. Custom Components: Always use PascalCase for your own component functions (e.g., UserCard, NavMenu) to distinguish them from the built-in Tag Helpers and maintain architectural clarity.

5. Logic to UI Comparison

Here is how a dynamic User Status component translates from SigPro logic to the final DOM structure, handled by the V2 "Saneamiento" engine.

javascript
// SigPro Component
 const UserStatus = (name, $online) => (
   Div({ class: 'flex items-center gap-2' }, [
     Span({ 
@@ -50,12 +50,12 @@
       class: 'w-3 h-3 bg-green-500 rounded-full' 
     }),
     P({ 
-      // Reactive text content
+      // Reactive text content via automatic $.watch
       class: () => $online() ? "text-bold" : "text-gray-400" 
     }, name)
   ])
-);
State ($online)Rendered HTML
true<div class="flex..."><span class="w-3..."></span><p class="text-bold">John</p></div>
false<div class="flex..."><span hidden class="w-3..."></span><p class="text-gray-400">John</p></div>
- +);
State ($online)Rendered HTMLMemory Management
true<div class="flex..."><span class="w-3..."></span><p class="text-bold">John</p></div>Watcher active
false<div class="flex..."><span hidden class="w-3..."></span><p class="text-gray-400">John</p></div>Attribute synced
+ \ No newline at end of file diff --git a/docs/api/view.html b/docs/api/view.html deleted file mode 100644 index bab404f..0000000 --- a/docs/api/view.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - 🖼️ Component Lifecycle: $.view( ) | SigPro - - - - - - - - - - - - - - - -
Skip to content

🖼️ Component Lifecycle: $.view( )

The $.view function is a specialized wrapper used to manage the lifecycle of a UI component. It tracks all signals, effects, and DOM elements created within it, providing a single point of destruction to prevent memory leaks.

🛠 Function Signature

typescript
$.view(renderFn: Function): RuntimeObject
ParameterTypeRequiredDescription
renderFnFunctionYesA function that returns a DOM Node or an array of Nodes.

Returns: A Runtime object containing:

  • container: A div (with display: contents) holding the rendered content.
  • destroy(): A function that unmounts the view and cleans up all internal effects/listeners.

📖 Usage Patterns

1. Basic Component Wrapper

When you wrap logic in $.view, SigPro creates a "boundary".

javascript
const myView = $.view(() => {
-  const count = $(0);
-  
-  // This effect is "owned" by this view
-  $.effect(() => console.log(count()));
-
-  return Div([
-    H1("Internal View"),
-    Button({ onclick: () => count(c => c + 1) }, "Add")
-  ]);
-});
-
-// To show it:
-document.body.appendChild(myView.container);
-
-// To kill it (removes from DOM and stops all internal effects):
-myView.destroy();

2. Manual Cleanups with onCleanup

The renderFn receives a helper object that allows you to register custom cleanup logic (like closing a WebSocket or a third-party library instance).

javascript
const ChartComponent = () => $.view(({ onCleanup }) => {
-  const socket = new WebSocket("ws://...");
-
-  onCleanup(() => {
-    socket.close();
-    console.log("Cleanup: WebSocket closed");
-  });
-
-  return Div({ class: "chart-container" });
-});

🏗️ Internal Architecture

When $.view is called, it performs the following:

  1. Isolation: It creates a new Set of cleanups.
  2. Execution: It runs your function and captures any $.effect or child $.view created during execution.
  3. Container: It wraps the result in a display: contents element (which doesn't affect your CSS layout).
  4. Disposal: When destroy() is called, it recursively calls all cleanup functions and removes the container from the DOM.

💡 Why use $.view?

  • Automatic Cleanup: You don't have to manually stop every effect when a user navigates away.
  • Router Integration: The SigPro Router ($.router) uses $.view internally to swap pages and clean up the previous one automatically.
  • Performance: It ensures that background processes (like intervals or observers) stop as soon as the element is no longer visible.
- - - - \ No newline at end of file diff --git a/docs/api/watch.html b/docs/api/watch.html new file mode 100644 index 0000000..580c519 --- /dev/null +++ b/docs/api/watch.html @@ -0,0 +1,58 @@ + + + + + + ⚡ Reactivity Control: $.watch( ) | SigPro + + + + + + + + + + + + + + + +
Skip to content

⚡ Reactivity Control: $.watch( )

The $.watch function is the reactive engine of SigPro. It allows you to execute code automatically when signals change. In V2, $.watch is polymorphic: it can track dependencies automatically or follow an explicit list.

🛠 Function Signature

typescript
// Automatic Mode (Magic Tracking)
+$.watch(callback: Function): StopFunction
+
+// Explicit Mode (Isolated Dependencies)
+$.watch(deps: Signal[], callback: Function): StopFunction
ParameterTypeRequiredDescription
target / depsFunctionArrayYes
callbackFunctionOnly in ExplicitThe code that will run when the deps change.

Returns: A StopFunction that, when called, destroys the watcher and releases memory.


📖 Usage Patterns

1. Automatic Mode (Default)

Any signal you "touch" inside the callback becomes a dependency. SigPro tracks them behind the scenes.

javascript
const count = $(0);
+
+$.watch(() => {
+  // Re-runs every time 'count' changes
+  console.log(`Count is: ${count()}`);
+});

2. Explicit Mode (Advanced Saneamiento) 🚀

This mode isolates execution. The callback only triggers when the signals in the array change. Any other signal accessed inside the callback will NOT trigger a re-run. This is the "gold standard" for Routers and heavy components.

javascript
const sPath = $("/home");
+const user = $("Admin");
+
+$.watch([sPath], () => {
+  // Only triggers when 'sPath' changes.
+  // Changes to 'user' will NOT trigger this, preventing accidental re-renders.
+  console.log(`Navigating to ${sPath()} as ${user()}`);
+});

3. Automatic Cleanup

If your logic creates timers, event listeners, or other reactive effects, SigPro tracks them as "children" of the current watch. When the watcher re-runs or stops, it kills everything inside automatically.

javascript
$.watch(() => {
+  const timer = setInterval(() => console.log("Tick"), 1000);
+  
+  // Register a manual cleanup if needed
+  // Or simply rely on SigPro to kill nested $.watch() calls
+  return () => clearInterval(timer);
+});

🛑 Stopping a Watcher

Call the returned function to manually kill the watcher. This is essential for manual DOM injections (like Toasts) or long-lived background processes.

javascript
const stop = $.watch(() => console.log(count()));
+
+// Later...
+stop(); // The link between the signal and this code is physically severed.

💡 Pro Tip: The Microtask Queue

SigPro batches updates. If you update multiple signals in the same execution block, the watcher will only fire once at the end of the task.

javascript
const a = $(0);
+const b = $(0);
+
+$.watch(() => console.log(a(), b()));
+
+// This triggers only ONE re-run.
+a(1);
+b(2);
+ + + + \ No newline at end of file diff --git a/docs/assets/api_effect.md.jV8KzXq5.js b/docs/assets/api_effect.md.jV8KzXq5.js deleted file mode 100644 index ccae179..0000000 --- a/docs/assets/api_effect.md.jV8KzXq5.js +++ /dev/null @@ -1,33 +0,0 @@ -import{_ as i,o as a,c as t,ae as n}from"./chunks/framework.C8AWLET_.js";const E=JSON.parse('{"title":"⚡ Side Effects: $.effect( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/effect.md","filePath":"api/effect.md"}'),e={name:"api/effect.md"};function h(l,s,p,k,r,d){return a(),t("div",null,[...s[0]||(s[0]=[n(`

⚡ Side Effects: $.effect( )

The $.effect function allows you to run a piece of code whenever the signals it depends on are updated. It automatically tracks any signal called within its body.

🛠 Function Signature

typescript
$.effect(callback: Function): StopFunction
ParameterTypeRequiredDescription
callbackFunctionYesThe code to run. It will execute immediately and then re-run on dependency changes.

Returns: A StopFunction that, when called, cancels the effect and prevents further executions.


📖 Usage Patterns

1. Basic Tracking

Any signal you "touch" inside the effect becomes a dependency.

javascript
const count = $(0);
-
-$.effect(() => {
-  // This runs every time 'count' changes
-  console.log(\`The count is now: \${count()}\`);
-});
-
-count(1); // Console: "The count is now: 1"

2. Manual Cleanup

If your effect creates something that needs to be destroyed (like a timer or a global event listener), you can return a cleanup function.

javascript
$.effect(() => {
-  const timer = setInterval(() => console.log("Tick"), 1000);
-
-  // SigPro will run this BEFORE the next effect execution 
-  // or when the effect is stopped.
-  return () => clearInterval(timer);
-});

3. Nesting & Automatic Cleanup

If you create a signal or another effect inside an effect, SigPro tracks them as "children". When the parent effect re-runs or stops, all children are automatically cleaned up to prevent memory leaks.

javascript
$.effect(() => {
-  if (isLoggedIn()) {
-    // This sub-effect is only active while 'isLoggedIn' is true
-    $.effect(() => {
-      console.log("Fetching user data...");
-    });
-  }
-});

🛑 Stopping an Effect

You can stop an effect manually by calling the function it returns. This is useful for one-time operations or complex logic.

javascript
const stop = $.effect(() => {
-  console.log(count());
-});
-
-// Later...
-stop(); // The effect is destroyed and will never run again.

💡 Pro Tip: Batching

SigPro uses a Microtask Queue to handle updates. If you update multiple signals at once, the effect will only run once at the end of the current task.

javascript
const a = $(0);
-const b = $(0);
-
-$.effect(() => console.log(a(), b()));
-
-// This triggers only ONE re-run, not two.
-a(1);
-b(2);
`,25)])])}const o=i(e,[["render",h]]);export{E as __pageData,o as default}; diff --git a/docs/assets/api_effect.md.jV8KzXq5.lean.js b/docs/assets/api_effect.md.jV8KzXq5.lean.js deleted file mode 100644 index d8769e3..0000000 --- a/docs/assets/api_effect.md.jV8KzXq5.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as i,o as a,c as t,ae as n}from"./chunks/framework.C8AWLET_.js";const E=JSON.parse('{"title":"⚡ Side Effects: $.effect( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/effect.md","filePath":"api/effect.md"}'),e={name:"api/effect.md"};function h(l,s,p,k,r,d){return a(),t("div",null,[...s[0]||(s[0]=[n("",25)])])}const o=i(e,[["render",h]]);export{E as __pageData,o as default}; diff --git a/docs/assets/api_html.md.COPskx0H.js b/docs/assets/api_html.md.BPbZMZR1.js similarity index 56% rename from docs/assets/api_html.md.COPskx0H.js rename to docs/assets/api_html.md.BPbZMZR1.js index 90717ae..043f3ac 100644 --- a/docs/assets/api_html.md.COPskx0H.js +++ b/docs/assets/api_html.md.BPbZMZR1.js @@ -1,4 +1,4 @@ -import{_ as i,o as t,c as a,ae as e}from"./chunks/framework.C8AWLET_.js";const c=JSON.parse('{"title":"🏗️ The DOM Factory: $.html( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/html.md","filePath":"api/html.md"}'),n={name:"api/html.md"};function l(h,s,p,r,k,o){return t(),a("div",null,[...s[0]||(s[0]=[e(`

🏗️ The DOM Factory: $.html( )

$.html is the internal engine that creates, attributes, and attaches reactivity to DOM elements. It is the foundation for all Tag Constructors in SigPro.

🛠 Function Signature

typescript
$.html(tagName: string, props?: Object, children?: any[] | any): HTMLElement
ParameterTypeRequiredDescription
tagNamestringYesValid HTML tag name (e.g., "div", "button").
propsObjectNoHTML attributes, event listeners, and reactive bindings.
childrenanyNoNested elements, text strings, or reactive functions.

📖 Key Features

1. Attribute Handling

SigPro intelligently decides how to apply each property:

  • Standard Props: Applied via setAttribute or direct property assignment.
  • Boolean Props: Uses toggleAttribute (e.g., checked, disabled, hidden).
  • Class Names: Supports class or className interchangeably.

2. Event Listeners & Modifiers

Events are defined by the on prefix. SigPro supports Dot Notation for common event operations:

javascript
$.html("button", {
+import{_ as i,o as t,c as a,ae as e}from"./chunks/framework.C8AWLET_.js";const c=JSON.parse('{"title":"🏗️ The DOM Factory: $.html( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/html.md","filePath":"api/html.md"}'),n={name:"api/html.md"};function l(h,s,p,r,o,k){return t(),a("div",null,[...s[0]||(s[0]=[e(`

🏗️ The DOM Factory: $.html( )

$.html is the internal engine that creates, attributes, and attaches reactivity to DOM elements. In V2, it uses $.watch to maintain a live, high-performance link between your Signals and the Document Object Model.

🛠 Function Signature

typescript
$.html(tagName: string, props?: Object, children?: any[] | any): HTMLElement
ParameterTypeRequiredDescription
tagNamestringYesValid HTML tag name (e.g., "div", "button").
propsObjectNoHTML attributes, event listeners, and reactive bindings.
childrenanyNoNested elements, text strings, or reactive functions.

📖 Key Features

1. Attribute Handling

SigPro intelligently decides how to apply each property:

  • Standard Props: Applied via setAttribute or direct property assignment.
  • Boolean Props: Uses toggleAttribute (e.g., checked, disabled, hidden).
  • Class Names: Supports class or className interchangeably.

2. Event Listeners & Modifiers

Events are defined by the on prefix. SigPro supports Dot Notation for common event operations:

javascript
$.html("button", {
   // e.preventDefault() is called automatically
   "onsubmit.prevent": (e) => save(e), 
   
@@ -7,18 +7,18 @@ import{_ as i,o as t,c as a,ae as e}from"./chunks/framework.C8AWLET_.js";const c
   
   // { once: true } listener option
   "onclick.once": () => console.log("Runs only once")
-}, "Click Me");

3. Reactive Attributes

If an attribute value is a function (like a Signal), $.html creates an internal $.effect to keep the DOM in sync with the state.

javascript
$.html("div", {
+}, "Click Me");

3. Reactive Attributes

If an attribute value is a function (like a Signal), $.html creates an internal $.watch to keep the DOM in sync with the state.

javascript
$.html("div", {
   // Updates the class whenever 'theme()' changes
   class: () => theme() === "dark" ? "bg-black" : "bg-white"
-});

4. Reactive Children

Children can be static or dynamic. When a child is a function, SigPro creates a reactive boundary for that specific part of the DOM.

javascript
$.html("div", {}, [
+});

4. Reactive Children

Children can be static or dynamic. When a child is a function, SigPro creates a reactive boundary using $.watch for that specific part of the DOM.

javascript
$.html("div", {}, [
   H1("Static Title"),
   // Only this text node re-renders when 'count' changes
   () => \`Current count: \${count()}\`
 ]);

🔄 Two-Way Binding Operator ($)

When a property starts with $, $.html enables bidirectional synchronization. This is primarily used for form inputs.

javascript
$.html("input", {
   type: "text",
   $value: username // Syncs input value <-> signal
-});

🧹 Automatic Cleanup

Every element created with $.html gets a hidden ._cleanups property (a Set).

  • When SigPro removes an element via $.view or $.router, it automatically executes all functions stored in this Set (stopping effects, removing listeners, etc.).

💡 Tag Constructors (The Shortcuts)

Instead of writing $.html("div", ...) every time, SigPro provides PascalCase global functions:

javascript
// This:
+});

🧹 Memory Management (Internal)

Every element created with $.html is "self-aware" regarding its reactive dependencies.

  • ._cleanups: A hidden Set attached to the element that stores all stop() functions from its internal $.watch calls and event listeners.
  • Lifecycle: When an element is removed by a Controller ($.If, $.For, or $.router), SigPro performs a recursive "sweep" to execute these cleanups, ensuring zero memory leaks.

💡 Tag Constructors (The Shortcuts)

Instead of writing $.html("div", ...) every time, SigPro provides PascalCase global functions:

javascript
// This:
 Div({ class: "wrapper" }, [ Span("Hello") ])
 
 // Is exactly equivalent to:
-$.html("div", { class: "wrapper" }, [ $.html("span", {}, "Hello") ])
`,30)])])}const E=i(n,[["render",l]]);export{c as __pageData,E as default}; +$.html("div", { class: "wrapper" }, [ $.html("span", {}, "Hello") ])
`,30)])])}const g=i(n,[["render",l]]);export{c as __pageData,g as default}; diff --git a/docs/assets/api_html.md.COPskx0H.lean.js b/docs/assets/api_html.md.BPbZMZR1.lean.js similarity index 62% rename from docs/assets/api_html.md.COPskx0H.lean.js rename to docs/assets/api_html.md.BPbZMZR1.lean.js index f4b5067..e13d045 100644 --- a/docs/assets/api_html.md.COPskx0H.lean.js +++ b/docs/assets/api_html.md.BPbZMZR1.lean.js @@ -1 +1 @@ -import{_ as i,o as t,c as a,ae as e}from"./chunks/framework.C8AWLET_.js";const c=JSON.parse('{"title":"🏗️ The DOM Factory: $.html( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/html.md","filePath":"api/html.md"}'),n={name:"api/html.md"};function l(h,s,p,r,k,o){return t(),a("div",null,[...s[0]||(s[0]=[e("",30)])])}const E=i(n,[["render",l]]);export{c as __pageData,E as default}; +import{_ as i,o as t,c as a,ae as e}from"./chunks/framework.C8AWLET_.js";const c=JSON.parse('{"title":"🏗️ The DOM Factory: $.html( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/html.md","filePath":"api/html.md"}'),n={name:"api/html.md"};function l(h,s,p,r,o,k){return t(),a("div",null,[...s[0]||(s[0]=[e("",30)])])}const g=i(n,[["render",l]]);export{c as __pageData,g as default}; diff --git a/docs/assets/api_ignore.md.CxKek-H-.js b/docs/assets/api_ignore.md.CxKek-H-.js deleted file mode 100644 index 4bf442b..0000000 --- a/docs/assets/api_ignore.md.CxKek-H-.js +++ /dev/null @@ -1,27 +0,0 @@ -import{_ as i,o as a,c as n,ae as t}from"./chunks/framework.C8AWLET_.js";const o=JSON.parse('{"title":"🛑 Untracking: $.ignore( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/ignore.md","filePath":"api/ignore.md"}'),e={name:"api/ignore.md"};function l(h,s,p,k,r,g){return a(),n("div",null,[...s[0]||(s[0]=[t(`

🛑 Untracking: $.ignore( )

The $.ignore function allows you to read a signal's value inside an effect or a computed signal without creating a dependency.

🛠 Function Signature

typescript
$.ignore(callback: Function): any
ParameterTypeRequiredDescription
callbackFunctionYesA function where signals can be read "silently".

Returns: Whatever the callback function returns.


📖 Usage Patterns

1. Preventing Dependency Tracking

Normally, reading a signal inside $.effect makes the effect re-run when that signal changes. $.ignore breaks this link.

javascript
const count = $(0);
-const logLabel = $("System Log");
-
-$.effect(() => {
-  // This effect tracks 'count'...
-  const currentCount = count();
-  
-  // ...but NOT 'logLabel'. 
-  // Changing 'logLabel' will NOT re-run this effect.
-  const label = $.ignore(() => logLabel());
-  
-  console.log(\`\${label}: \${currentCount}\`);
-});
-
-count(1);     // Console: "System Log: 1" (Triggers re-run)
-logLabel("UI"); // Nothing happens in console (Ignored)

2. Reading State in Event Handlers

Inside complex UI logic, you might want to take a "snapshot" of a signal without triggering a reactive chain.

javascript
const handleClick = () => {
-  // Accessing state without letting the caller know we touched it
-  const data = $.ignore(() => mySignal());
-  process(data);
-};

3. Avoiding Infinite Loops

If you need to write to a signal based on its own value inside an effect (and you aren't using the functional updater), $.ignore prevents the effect from triggering itself.

javascript
$.effect(() => {
-  const value = someSignal();
-  
-  if (value > 100) {
-    // We update the signal, but we ignore the read to avoid a loop
-    $.ignore(() => someSignal(0));
-  }
-});

💡 Why use it?

  • Performance: Prevents expensive effects from running when non-essential data changes.
  • Logic Control: Allows "sampling" a signal at a specific point in time.
  • Safety: Essential for complex state orchestrations where circular dependencies might occur.
`,20)])])}const c=i(e,[["render",l]]);export{o as __pageData,c as default}; diff --git a/docs/assets/api_ignore.md.CxKek-H-.lean.js b/docs/assets/api_ignore.md.CxKek-H-.lean.js deleted file mode 100644 index 2ce2684..0000000 --- a/docs/assets/api_ignore.md.CxKek-H-.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as i,o as a,c as n,ae as t}from"./chunks/framework.C8AWLET_.js";const o=JSON.parse('{"title":"🛑 Untracking: $.ignore( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/ignore.md","filePath":"api/ignore.md"}'),e={name:"api/ignore.md"};function l(h,s,p,k,r,g){return a(),n("div",null,[...s[0]||(s[0]=[t("",20)])])}const c=i(e,[["render",l]]);export{o as __pageData,c as default}; diff --git a/docs/assets/api_mount.md.BiKjH18I.js b/docs/assets/api_mount.md.BiKjH18I.js new file mode 100644 index 0000000..18befd1 --- /dev/null +++ b/docs/assets/api_mount.md.BiKjH18I.js @@ -0,0 +1,16 @@ +import{_ as s,o as i,c as a,ae as e}from"./chunks/framework.C8AWLET_.js";const k=JSON.parse('{"title":"🔌 Application Mounter: $.mount( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/mount.md","filePath":"api/mount.md"}'),n={name:"api/mount.md"};function l(o,t,h,r,p,d){return i(),a("div",null,[...t[0]||(t[0]=[e(`

🔌 Application Mounter: $.mount( )

The $.mount function is the entry point of your reactive world. It bridges the gap between your SigPro logic and the browser's Real DOM by injecting a component into the document and initializing its reactive lifecycle.

🛠️ Function Signature

typescript
$.mount(node: Function | HTMLElement, target?: string | HTMLElement): RuntimeObject
ParameterTypeDefaultDescription
nodeFunction or NodeRequiredThe component function or DOM element to render.
targetstring or Nodedocument.bodyCSS selector or DOM element where the app will live.

Returns: A Runtime object containing the container and a destroy() method to wipe all reactivity and DOM nodes.


📖 Common Usage Scenarios

1. The SPA Entry Point

In a Single Page Application, you typically mount your main component to the body or a root div. SigPro manages the entire view from that point.

javascript
import { $ } from './sigpro.js';
+import App from './App.js';
+
+// Mounts your main App component
+$.mount(App, '#app-root');

2. Reactive "Islands"

SigPro is perfect for adding reactivity to static pages. You can mount small widgets into specific parts of an existing HTML layout.

javascript
const Counter = () => {
+  const count = $(0);
+  return Button({ onclick: () => count(c => c + 1) }, [
+    "Clicks: ", count
+  ]);
+};
+
+// Mount only the counter into a specific sidebar div
+$.mount(Counter, '#sidebar-widget');

🔄 How it Works (Lifecycle & Saneamiento)

When $.mount is executed, it performs these critical steps to ensure a leak-free environment:

  1. Duplicate Detection: If you call $.mount on a target that already has a SigPro instance, it automatically calls .destroy() on the previous instance. This prevents "Zombie Effects" from stacking in memory.
  2. Internal Scoping: It executes the component function inside an internal Reactive Owner. This captures every $.watch and event listener created during the render.
  3. Target Injection: It clears the target using replaceChildren() and appends the new component.
  4. Runtime Creation: It returns a control object:
    • container: The actual DOM element created.
    • destroy(): The "kill switch" that runs all cleanups, stops all watchers, and removes the element from the DOM.

🛑 Manual Unmounting

While SigPro handles most cleanups automatically (via $.If, $.For, and $.router), you can manually destroy any mounted instance. This is vital for imperatively managed UI like Toasts or Modals.

javascript
const instance = $.mount(MyToast, '#toast-container');
+
+// Later, to remove the toast and kill its reactivity:
+instance.destroy();

💡 Summary Cheat Sheet

GoalCode Pattern
Mount to body$.mount(App)
Mount to CSS Selector$.mount(App, '#root')
Mount to DOM Node$.mount(App, myElement)
Clean & Re-mountCalling $.mount again on the same target
Total Saneamientoconst app = $.mount(App); app.destroy();
`,25)])])}const g=s(n,[["render",l]]);export{k as __pageData,g as default}; diff --git a/docs/assets/api_mount.md.BiKjH18I.lean.js b/docs/assets/api_mount.md.BiKjH18I.lean.js new file mode 100644 index 0000000..5893367 --- /dev/null +++ b/docs/assets/api_mount.md.BiKjH18I.lean.js @@ -0,0 +1 @@ +import{_ as s,o as i,c as a,ae as e}from"./chunks/framework.C8AWLET_.js";const k=JSON.parse('{"title":"🔌 Application Mounter: $.mount( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/mount.md","filePath":"api/mount.md"}'),n={name:"api/mount.md"};function l(o,t,h,r,p,d){return i(),a("div",null,[...t[0]||(t[0]=[e("",25)])])}const g=s(n,[["render",l]]);export{k as __pageData,g as default}; diff --git a/docs/assets/api_mount.md.CRwLyxt8.js b/docs/assets/api_mount.md.CRwLyxt8.js deleted file mode 100644 index 12cd788..0000000 --- a/docs/assets/api_mount.md.CRwLyxt8.js +++ /dev/null @@ -1,27 +0,0 @@ -import{_ as i,o as a,c as t,ae as e}from"./chunks/framework.C8AWLET_.js";const c=JSON.parse('{"title":"🔌 Application Mounter: $.mount( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/mount.md","filePath":"api/mount.md"}'),n={name:"api/mount.md"};function l(h,s,p,o,r,k){return a(),t("div",null,[...s[0]||(s[0]=[e(`

🔌 Application Mounter: $.mount( )

The $.mount function is the entry point of your reactive world. It bridges the gap between your SigPro logic and the browser's Real DOM by injecting a component into the document.

1. Function Signature

typescript
$.mount(node: Function | HTMLElement, target?: string | HTMLElement): RuntimeObject
ParameterTypeDefaultDescription
nodeFunction or NodeRequiredThe component function or DOM element to render.
targetstring or Nodedocument.bodyCSS selector or DOM element where the app will live.

Returns: A Runtime object containing the container and a destroy() method.


2. Common Usage Scenarios

A. The "Clean Slate" (SPA Entry)

In a modern Single Page Application, you typically want SigPro to manage the entire view. By default, if no target is provided, it mounts to document.body.

javascript
import { $ } from './sigpro.js';
-import App from './App.js';
-
-// Mounts your main App component directly to the body
-$.mount(App);

B. Targeting a Specific Container

If your HTML has a predefined structure, you can tell SigPro exactly where to render by passing a CSS selector or a direct reference.

html
<div id="sidebar"></div>
-<main id="app-root"></main>
javascript
// Mount using a CSS selector
-$.mount(MyComponent, '#app-root');
-
-// Mount using a direct DOM reference
-const sidebar = document.querySelector('#sidebar');
-$.mount(SidebarComponent, sidebar);

C. Creating "Reactive Islands"

SigPro is excellent for "sprinkling" reactivity onto legacy or static pages. You can inject small reactive widgets into any part of an existing HTML layout.

javascript
// A small reactive widget
-const CounterWidget = () => {
-  const count = $(0);
-  return Button({ onclick: () => count(c => c + 1) }, [
-    "Clicks: ", count
-  ]);
-};
-
-// Mount it into a specific div in your static HTML
-$.mount(CounterWidget, '#counter-container');

3. How it Works (Lifecycle)

When $.mount is executed, it performs these critical steps:

  1. Resolution & Wrapping: If you pass a Function, SigPro wraps it in a $.view(). This starts tracking all internal signals and effects.
  2. Target Clearance: It uses target.replaceChildren(). This efficiently wipes any existing HTML or "zombie" nodes inside the target before mounting.
  3. Injection: The component's container is appended to the target.
  4. Memory Management: It stores the Runtime instance associated with that DOM element. If you call $.mount again on the same target, SigPro automatically destroys the previous app to prevent memory leaks.

4. Global vs. Local Scope

The "Framework" Way (Global)

By importing your core in your entry file, SigPro automatically initializes global Tag Constructors (Div, Span, H1, etc.). This allows for a clean, declarative DX across your entire project.

javascript
// main.js
-import './sigpro.js'; 
-
-// Now any file can simply do:
-$.mount(() => H1("Global SigPro App"));

The "Library" Way (Local)

If you prefer to avoid global variables, you can use the low-level $.html factory to create elements locally.

javascript
import { $ } from './sigpro.js';
-
-const myNode = $.html('div', { class: 'widget' }, 'Local Instance');
-$.mount(myNode, '#widget-target');

5. Summary Cheat Sheet

GoalCode Pattern
Mount to body$.mount(App)
Mount to ID$.mount(App, '#root')
Mount to Element$.mount(App, myElement)
Mount raw Node$.mount(Div("Hello"), '#id')
Unmount/Destroyconst app = $.mount(App); app.destroy();
`,33)])])}const g=i(n,[["render",l]]);export{c as __pageData,g as default}; diff --git a/docs/assets/api_mount.md.CRwLyxt8.lean.js b/docs/assets/api_mount.md.CRwLyxt8.lean.js deleted file mode 100644 index 56af600..0000000 --- a/docs/assets/api_mount.md.CRwLyxt8.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as i,o as a,c as t,ae as e}from"./chunks/framework.C8AWLET_.js";const c=JSON.parse('{"title":"🔌 Application Mounter: $.mount( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/mount.md","filePath":"api/mount.md"}'),n={name:"api/mount.md"};function l(h,s,p,o,r,k){return a(),t("div",null,[...s[0]||(s[0]=[e("",33)])])}const g=i(n,[["render",l]]);export{c as __pageData,g as default}; diff --git a/docs/assets/api_tags.md.VliNqepa.js b/docs/assets/api_tags.md.CW_zjfl9.js similarity index 53% rename from docs/assets/api_tags.md.VliNqepa.js rename to docs/assets/api_tags.md.CW_zjfl9.js index ad52efb..515b5ed 100644 --- a/docs/assets/api_tags.md.VliNqepa.js +++ b/docs/assets/api_tags.md.CW_zjfl9.js @@ -1,26 +1,26 @@ -import{_ as i,o as a,c as t,ae as e}from"./chunks/framework.C8AWLET_.js";const c=JSON.parse('{"title":"🎨 Global Tag Helpers","description":"","frontmatter":{},"headers":[],"relativePath":"api/tags.md","filePath":"api/tags.md"}'),n={name:"api/tags.md"};function l(h,s,o,p,r,d){return a(),t("div",null,[...s[0]||(s[0]=[e(`

🎨 Global Tag Helpers

In SigPro, you don't need to manually type $.html('div', ...) for every element. To keep your code declarative and readable, the engine automatically generates Global Helper Functions for all standard HTML5 tags upon initialization.

1. How it Works

SigPro iterates through a manifest of standard HTML tags and attaches a wrapper function for each one directly to the window object. This creates a specialized DSL (Domain Specific Language) that looks like a template engine but is 100% standard JavaScript.

  • Under the hood: $.html('button', { onclick: ... }, 'Click')
  • SigPro Style: Button({ onclick: ... }, 'Click')

2. The Complete Global Registry

The following functions are injected into the global scope (using PascalCase to prevent naming collisions with common JS variables) and are ready to use:

CategoryAvailable Global Functions
StructureDiv, Span, P, Section, Nav, Main, Header, Footer, Article, Aside
TypographyH1 to H6, Ul, Ol, Li, Dl, Dt, Dd, Strong, Em, Code, Pre, Small, B, U, Mark
InteractiveButton, A, Label, Br, Hr, Details, Summary, Dialog
FormsForm, Input, Select, Option, Textarea, Fieldset, Legend
TablesTable, Thead, Tbody, Tr, Th, Td, Tfoot, Caption
MediaImg, Canvas, Video, Audio, Svg, Iframe, Picture, Source

The SigPro Philosophy: Tags are not "magic strings" handled by a compiler. They are functional constructors. Every time you call Div(), you execute a pure JS function that returns a real, reactive DOM element.


3. Usage Patterns (Smart Arguments)

SigPro tag helpers are flexible. They automatically detect if you are passing attributes, children, or both.

A. Attributes + Children

The standard way to build structured UI.

javascript
Div({ class: 'container', id: 'main' }, [
+import{_ as i,o as a,c as t,ae as e}from"./chunks/framework.C8AWLET_.js";const c=JSON.parse('{"title":"🎨 Global Tag Helpers","description":"","frontmatter":{},"headers":[],"relativePath":"api/tags.md","filePath":"api/tags.md"}'),n={name:"api/tags.md"};function l(h,s,o,p,r,d){return a(),t("div",null,[...s[0]||(s[0]=[e(`

🎨 Global Tag Helpers

In SigPro V2, you don't need to manually type $.html('div', ...) for every element. To keep your code declarative and readable, the engine automatically generates Global Helper Functions for all standard HTML5 tags upon initialization.

1. How it Works

SigPro iterates through a manifest of standard HTML tags and attaches a wrapper function for each one directly to the window object. This creates a specialized DSL (Domain Specific Language) that looks like a template engine but is 100% standard JavaScript.

  • Under the hood: $.html('button', { onclick: ... }, 'Click')
  • SigPro Style: Button({ onclick: ... }, 'Click')

2. The Complete Global Registry

The following functions are injected into the global scope (using PascalCase to prevent naming collisions with common JS variables) and are ready to use:

CategoryAvailable Global Functions
StructureDiv, Span, P, Section, Nav, Main, Header, Footer, Article, Aside
TypographyH1 to H6, Ul, Ol, Li, Dl, Dt, Dd, Strong, Em, Code, Pre, Small, B, U, Mark
InteractiveButton, A, Label, Br, Hr, Details, Summary, Dialog
FormsForm, Input, Select, Option, Textarea, Fieldset, Legend
TablesTable, Thead, Tbody, Tr, Th, Td, Tfoot, Caption
MediaImg, Canvas, Video, Audio, Svg, Iframe, Picture, Source

The SigPro Philosophy: Tags are not "magic strings" handled by a compiler. They are functional constructors. Every time you call Div(), you execute a pure JS function that returns a real, reactive DOM element.


3. Usage Patterns (Smart Arguments)

SigPro tag helpers are flexible. They automatically detect if you are passing attributes, children, or both.

A. Attributes + Children

The standard way to build structured UI.

javascript
Div({ class: 'container', id: 'main' }, [
   H1("Welcome to SigPro"),
   P("The zero-VDOM framework.")
 ]);

B. Children Only (The "Skipper")

If you don't need attributes, you can skip the object and pass the content (string, array, or function) directly as the first argument.

javascript
Section([
   H2("Clean Syntax"),
   Button("I have no props!")
 ]);

C. Primitive Content

For simple tags, just pass a string or a number.

javascript
H1("Hello World"); 
-Span(42);

4. Reactive Power Examples

These helpers are natively wired into SigPro's reactivity. No manual useEffect or watch calls are needed.

Reactive Attributes

Simply pass a Signal (function) to any attribute. SigPro handles the rest.

javascript
const theme = $("light");
+Span(42);

4. Reactive Power V2

These helpers are natively wired into SigPro's $.watch engine. No manual effect management is needed; the lifecycle is tied to the DOM node.

Reactive Attributes

Simply pass a Signal (function) to any attribute. SigPro creates an internal $.watch to keep the DOM in sync.

javascript
const theme = $("light");
 
 Div({ 
-  // Updates 'class' automatically when theme() changes
+  // Updates 'class' automatically via internal $.watch
   class: () => \`app-box \${theme()}\` 
-}, "Themeable Box");

The Binding Operator ($)

Use the $ prefix for Two-Way Binding on inputs.

javascript
const search = $("");
+}, "Themeable Box");

The Binding Operator ($)

Use the $ prefix for Two-Way Binding on inputs. This bridges the Signal and the Input element bi-directionally.

javascript
const search = $("");
 
 Input({ 
   type: "text", 
   placeholder: "Search...",
   $value: search // UI updates Signal AND Signal updates UI
-});

Dynamic Lists

Combine tags with ui.For for high-performance list rendering.

javascript
const items = $(["Apple", "Banana", "Cherry"]);
+});

Dynamic Flow & Saneamiento

Combine tags with Core controllers for high-performance rendering. SigPro automatically cleans up the $.watch instances when nodes are removed.

javascript
const items = $(["Apple", "Banana", "Cherry"]);
 
 Ul({ class: "list-disc" }, [
-  ui.For(items, (item) => Li(item), (item) => item)
-]);

DANGER

⚠️ Important: Naming Conventions

Since SigPro injects these helpers into the global window object, follow these rules to avoid bugs:

  1. Avoid Shadowing: Don't name your local variables like the tags (e.g., const Div = ...). This will "hide" the SigPro helper.
  2. Custom Components: Always use PascalCase, UPPERCASE, or Underscore prefixes for your own component functions (e.g., UserCard, INPUT, or _Input) to distinguish them from the built-in Tag Helpers and avoid naming collisions.

6. Logic to UI Comparison

Here is how a dynamic User Status component translates from SigPro logic to the final DOM structure.

javascript
// SigPro Component
+  $.For(items, (item) => Li(item))
+]);

DANGER

⚠️ Important: Naming Conventions

Since SigPro injects these helpers into the global window object, follow these rules to avoid bugs:

  1. Avoid Shadowing: Don't name your local variables like the tags (e.g., const Div = ...). This will "hide" the SigPro helper.
  2. Custom Components: Always use PascalCase for your own component functions (e.g., UserCard, NavMenu) to distinguish them from the built-in Tag Helpers and maintain architectural clarity.

5. Logic to UI Comparison

Here is how a dynamic User Status component translates from SigPro logic to the final DOM structure, handled by the V2 "Saneamiento" engine.

javascript
// SigPro Component
 const UserStatus = (name, $online) => (
   Div({ class: 'flex items-center gap-2' }, [
     Span({ 
@@ -29,8 +29,8 @@ import{_ as i,o as a,c as t,ae as e}from"./chunks/framework.C8AWLET_.js";const c
       class: 'w-3 h-3 bg-green-500 rounded-full' 
     }),
     P({ 
-      // Reactive text content
+      // Reactive text content via automatic $.watch
       class: () => $online() ? "text-bold" : "text-gray-400" 
     }, name)
   ])
-);
State ($online)Rendered HTML
true<div class="flex..."><span class="w-3..."></span><p class="text-bold">John</p></div>
false<div class="flex..."><span hidden class="w-3..."></span><p class="text-gray-400">John</p></div>
`,41)])])}const g=i(n,[["render",l]]);export{c as __pageData,g as default}; +);
State ($online)Rendered HTMLMemory Management
true<div class="flex..."><span class="w-3..."></span><p class="text-bold">John</p></div>Watcher active
false<div class="flex..."><span hidden class="w-3..."></span><p class="text-gray-400">John</p></div>Attribute synced
`,41)])])}const g=i(n,[["render",l]]);export{c as __pageData,g as default}; diff --git a/docs/assets/api_tags.md.VliNqepa.lean.js b/docs/assets/api_tags.md.CW_zjfl9.lean.js similarity index 100% rename from docs/assets/api_tags.md.VliNqepa.lean.js rename to docs/assets/api_tags.md.CW_zjfl9.lean.js diff --git a/docs/assets/api_view.md.Bv8Rlx9s.js b/docs/assets/api_view.md.Bv8Rlx9s.js deleted file mode 100644 index 896a1b2..0000000 --- a/docs/assets/api_view.md.Bv8Rlx9s.js +++ /dev/null @@ -1,26 +0,0 @@ -import{_ as i,o as a,c as n,ae as t}from"./chunks/framework.C8AWLET_.js";const c=JSON.parse('{"title":"🖼️ Component Lifecycle: $.view( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/view.md","filePath":"api/view.md"}'),e={name:"api/view.md"};function l(h,s,p,k,r,o){return a(),n("div",null,[...s[0]||(s[0]=[t(`

🖼️ Component Lifecycle: $.view( )

The $.view function is a specialized wrapper used to manage the lifecycle of a UI component. It tracks all signals, effects, and DOM elements created within it, providing a single point of destruction to prevent memory leaks.

🛠 Function Signature

typescript
$.view(renderFn: Function): RuntimeObject
ParameterTypeRequiredDescription
renderFnFunctionYesA function that returns a DOM Node or an array of Nodes.

Returns: A Runtime object containing:

  • container: A div (with display: contents) holding the rendered content.
  • destroy(): A function that unmounts the view and cleans up all internal effects/listeners.

📖 Usage Patterns

1. Basic Component Wrapper

When you wrap logic in $.view, SigPro creates a "boundary".

javascript
const myView = $.view(() => {
-  const count = $(0);
-  
-  // This effect is "owned" by this view
-  $.effect(() => console.log(count()));
-
-  return Div([
-    H1("Internal View"),
-    Button({ onclick: () => count(c => c + 1) }, "Add")
-  ]);
-});
-
-// To show it:
-document.body.appendChild(myView.container);
-
-// To kill it (removes from DOM and stops all internal effects):
-myView.destroy();

2. Manual Cleanups with onCleanup

The renderFn receives a helper object that allows you to register custom cleanup logic (like closing a WebSocket or a third-party library instance).

javascript
const ChartComponent = () => $.view(({ onCleanup }) => {
-  const socket = new WebSocket("ws://...");
-
-  onCleanup(() => {
-    socket.close();
-    console.log("Cleanup: WebSocket closed");
-  });
-
-  return Div({ class: "chart-container" });
-});

🏗️ Internal Architecture

When $.view is called, it performs the following:

  1. Isolation: It creates a new Set of cleanups.
  2. Execution: It runs your function and captures any $.effect or child $.view created during execution.
  3. Container: It wraps the result in a display: contents element (which doesn't affect your CSS layout).
  4. Disposal: When destroy() is called, it recursively calls all cleanup functions and removes the container from the DOM.

💡 Why use $.view?

  • Automatic Cleanup: You don't have to manually stop every effect when a user navigates away.
  • Router Integration: The SigPro Router ($.router) uses $.view internally to swap pages and clean up the previous one automatically.
  • Performance: It ensures that background processes (like intervals or observers) stop as soon as the element is no longer visible.
`,22)])])}const E=i(e,[["render",l]]);export{c as __pageData,E as default}; diff --git a/docs/assets/api_view.md.Bv8Rlx9s.lean.js b/docs/assets/api_view.md.Bv8Rlx9s.lean.js deleted file mode 100644 index a703845..0000000 --- a/docs/assets/api_view.md.Bv8Rlx9s.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as i,o as a,c as n,ae as t}from"./chunks/framework.C8AWLET_.js";const c=JSON.parse('{"title":"🖼️ Component Lifecycle: $.view( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/view.md","filePath":"api/view.md"}'),e={name:"api/view.md"};function l(h,s,p,k,r,o){return a(),n("div",null,[...s[0]||(s[0]=[t("",22)])])}const E=i(e,[["render",l]]);export{c as __pageData,E as default}; diff --git a/docs/assets/api_watch.md.D7sOEzCX.js b/docs/assets/api_watch.md.D7sOEzCX.js new file mode 100644 index 0000000..3f59a1e --- /dev/null +++ b/docs/assets/api_watch.md.D7sOEzCX.js @@ -0,0 +1,33 @@ +import{_ as i,o as a,c as t,ae as n}from"./chunks/framework.C8AWLET_.js";const o=JSON.parse('{"title":"⚡ Reactivity Control: $.watch( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/watch.md","filePath":"api/watch.md"}'),e={name:"api/watch.md"};function h(l,s,p,k,r,d){return a(),t("div",null,[...s[0]||(s[0]=[n(`

⚡ Reactivity Control: $.watch( )

The $.watch function is the reactive engine of SigPro. It allows you to execute code automatically when signals change. In V2, $.watch is polymorphic: it can track dependencies automatically or follow an explicit list.

🛠 Function Signature

typescript
// Automatic Mode (Magic Tracking)
+$.watch(callback: Function): StopFunction
+
+// Explicit Mode (Isolated Dependencies)
+$.watch(deps: Signal[], callback: Function): StopFunction
ParameterTypeRequiredDescription
target / depsFunctionArrayYes
callbackFunctionOnly in ExplicitThe code that will run when the deps change.

Returns: A StopFunction that, when called, destroys the watcher and releases memory.


📖 Usage Patterns

1. Automatic Mode (Default)

Any signal you "touch" inside the callback becomes a dependency. SigPro tracks them behind the scenes.

javascript
const count = $(0);
+
+$.watch(() => {
+  // Re-runs every time 'count' changes
+  console.log(\`Count is: \${count()}\`);
+});

2. Explicit Mode (Advanced Saneamiento) 🚀

This mode isolates execution. The callback only triggers when the signals in the array change. Any other signal accessed inside the callback will NOT trigger a re-run. This is the "gold standard" for Routers and heavy components.

javascript
const sPath = $("/home");
+const user = $("Admin");
+
+$.watch([sPath], () => {
+  // Only triggers when 'sPath' changes.
+  // Changes to 'user' will NOT trigger this, preventing accidental re-renders.
+  console.log(\`Navigating to \${sPath()} as \${user()}\`);
+});

3. Automatic Cleanup

If your logic creates timers, event listeners, or other reactive effects, SigPro tracks them as "children" of the current watch. When the watcher re-runs or stops, it kills everything inside automatically.

javascript
$.watch(() => {
+  const timer = setInterval(() => console.log("Tick"), 1000);
+  
+  // Register a manual cleanup if needed
+  // Or simply rely on SigPro to kill nested $.watch() calls
+  return () => clearInterval(timer);
+});

🛑 Stopping a Watcher

Call the returned function to manually kill the watcher. This is essential for manual DOM injections (like Toasts) or long-lived background processes.

javascript
const stop = $.watch(() => console.log(count()));
+
+// Later...
+stop(); // The link between the signal and this code is physically severed.

💡 Pro Tip: The Microtask Queue

SigPro batches updates. If you update multiple signals in the same execution block, the watcher will only fire once at the end of the task.

javascript
const a = $(0);
+const b = $(0);
+
+$.watch(() => console.log(a(), b()));
+
+// This triggers only ONE re-run.
+a(1);
+b(2);
`,25)])])}const g=i(e,[["render",h]]);export{o as __pageData,g as default}; diff --git a/docs/assets/api_watch.md.D7sOEzCX.lean.js b/docs/assets/api_watch.md.D7sOEzCX.lean.js new file mode 100644 index 0000000..41b0821 --- /dev/null +++ b/docs/assets/api_watch.md.D7sOEzCX.lean.js @@ -0,0 +1 @@ +import{_ as i,o as a,c as t,ae as n}from"./chunks/framework.C8AWLET_.js";const o=JSON.parse('{"title":"⚡ Reactivity Control: $.watch( )","description":"","frontmatter":{},"headers":[],"relativePath":"api/watch.md","filePath":"api/watch.md"}'),e={name:"api/watch.md"};function h(l,s,p,k,r,d){return a(),t("div",null,[...s[0]||(s[0]=[n("",25)])])}const g=i(e,[["render",h]]);export{o as __pageData,g as default}; diff --git a/docs/assets/install.md.DmlvO98W.js b/docs/assets/install.md.C0utklUK.js similarity index 96% rename from docs/assets/install.md.DmlvO98W.js rename to docs/assets/install.md.C0utklUK.js index 5f996a7..6e502a1 100644 --- a/docs/assets/install.md.DmlvO98W.js +++ b/docs/assets/install.md.C0utklUK.js @@ -1,11 +1,11 @@ -import{_ as i,o as a,c as t,ae as n}from"./chunks/framework.C8AWLET_.js";const g=JSON.parse('{"title":"Installation & Setup","description":"","frontmatter":{},"headers":[],"relativePath":"install.md","filePath":"install.md"}'),l={name:"install.md"};function e(h,s,p,k,r,d){return a(),t("div",null,[...s[0]||(s[0]=[n(`

Installation & Setup

SigPro is designed to be drop-in ready. Whether you are building a complex application with a bundler or a simple reactive widget in a single HTML file, SigPro scales with your needs.

1. Installation

Choose the method that best fits your workflow:

bash
npm install sigpro
bash
pnpm add sigpro
bash
yarn add sigpro
bash
bun add sigpro
html
<script type="module">
+import{_ as i,o as a,c as t,ae as n}from"./chunks/framework.C8AWLET_.js";const g=JSON.parse('{"title":"Installation & Setup","description":"","frontmatter":{},"headers":[],"relativePath":"install.md","filePath":"install.md"}'),l={name:"install.md"};function e(h,s,p,k,r,d){return a(),t("div",null,[...s[0]||(s[0]=[n(`

Installation & Setup

SigPro is designed to be drop-in ready. Whether you are building a complex application with a bundler or a simple reactive widget in a single HTML file, SigPro scales with your needs.

1. Installation

Choose the method that best fits your workflow:

bash
npm install sigpro
bash
pnpm add sigpro
bash
yarn add sigpro
bash
bun add sigpro
html
<script type="module">
   // Import the core and UI components
   import { $ } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/+esm';
   import { UI } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/ui/+esm';
   
   // Initialize UI components globally
   UI($);
-</script>

2. Quick Start Examples

SigPro uses PascalCase for Tag Helpers (e.g., Div, Button) to provide a clean, component-like syntax without needing JSX.

javascript
// File: App.js
+</script>

2. Quick Start Examples

SigPro uses PascalCase for Tag Helpers (e.g., Div, Button) to provide a clean, component-like syntax without needing JSX.

javascript
// File: App.js
 import { $ } from 'sigpro'; 
 
 export const App = () => {
diff --git a/docs/assets/install.md.DmlvO98W.lean.js b/docs/assets/install.md.C0utklUK.lean.js
similarity index 100%
rename from docs/assets/install.md.DmlvO98W.lean.js
rename to docs/assets/install.md.C0utklUK.lean.js
diff --git a/docs/assets/ui_quick.md.Bzj-nQ2u.js b/docs/assets/ui_quick.md.CsppjR8J.js
similarity index 59%
rename from docs/assets/ui_quick.md.Bzj-nQ2u.js
rename to docs/assets/ui_quick.md.CsppjR8J.js
index 3d156a9..f129766 100644
--- a/docs/assets/ui_quick.md.Bzj-nQ2u.js
+++ b/docs/assets/ui_quick.md.CsppjR8J.js
@@ -1,4 +1,4 @@
-import{_ as i,o as a,c as t,ae as e}from"./chunks/framework.C8AWLET_.js";const c=JSON.parse('{"title":"🧩 UI Components (WIP)","description":"","frontmatter":{},"headers":[],"relativePath":"ui/quick.md","filePath":"ui/quick.md"}'),n={name:"ui/quick.md"};function l(h,s,p,o,k,r){return a(),t("div",null,[...s[0]||(s[0]=[e(`

🧩 UI Components (WIP)

Status: Work In Progress. > These are high-level, complex visual components designed to speed up development. They often replace native HTML elements with "superpowered" versions that handle their own internal logic, reactivity, and accessibility.

1. What are UI Components?

Unlike Tag Helpers (which are just functional mirrors of HTML tags), SigPro UI Components are smart abstractions.

  • Stateful: They manage complex internal states (like date ranges, search filtering, or API lifecycles).
  • Reactive: Attributes prefixed with $ are automatically tracked.
  • Self-Cleaning: They automatically use _cleanups to destroy observers or event listeners when removed from the DOM.
  • Themed: Fully compatible with Tailwind CSS and DaisyUI themes.

2. The UI Registry (Available Now)

CategoryComponents
Logic & FlowIf, For, Json
Forms & InputsButton, Input, Select, Autocomplete, Datepicker, Colorpicker, CheckBox, Radio, Range, Rating, Swap
FeedbackAlert, Toast, Modal, Loading, Badge, Tooltip, Indicator
NavigationNavbar, Menu, Drawer, Tabs, Accordion, Dropdown
Data & LayoutRequest, Response, Grid (AG-Grid), List, Stack, Timeline, Stat, Fieldset, Fab

3. Examples with "Superpowers"

A. The Declarative API Flow (Request & Response)

Instead of manually managing loading and error flags, use these two together to handle data fetching elegantly.

javascript
// 1. Define the request (it tracks dependencies automatically)
+import{_ as i,o as a,c as e,ae as t}from"./chunks/framework.C8AWLET_.js";const c=JSON.parse('{"title":"🧩 UI Components (WIP)","description":"","frontmatter":{},"headers":[],"relativePath":"ui/quick.md","filePath":"ui/quick.md"}'),n={name:"ui/quick.md"};function l(h,s,p,o,r,k){return a(),e("div",null,[...s[0]||(s[0]=[t(`

🧩 UI Components (WIP)

Status: Work In Progress. > These are high-level, complex visual components designed to speed up development. They replace native HTML elements with "superpowered" versions that handle their own internal logic, reactivity, and professional styling.

⚠️ Prerequisites

To ensure all components render correctly with their reactive themes and states, your project must have the following versions installed:

  • Tailwind CSS v4+: For the new engine performance and modern CSS variables.
  • DaisyUI v5+: Required for the updated theme-selectors and improved component classes used in the SigPro UI library.

1. What are UI Components?

Unlike Tag Helpers (which are just functional mirrors of HTML tags), SigPro UI Components are smart abstractions:

  • Stateful: They manage complex internal states (like date ranges, search filtering, or API lifecycles).
  • Reactive: Attributes prefixed with $ are automatically tracked via $.watch.
  • Self-Sane: They automatically use ._cleanups to destroy observers or event listeners when removed from the DOM.
  • Themed: Fully compatible with the DaisyUI v5 theme system and Tailwind v4 utility classes.

2. The UI Registry (Available Now)

CategoryComponents
Forms & InputsButton, Input, Select, Autocomplete, Datepicker, Colorpicker, CheckBox, Radio, Range, Rating, Swap
FeedbackAlert, Toast, Modal, Loading, Badge, Tooltip, Indicator
NavigationNavbar, Menu, Drawer, Tabs, Accordion, Dropdown
Data & LayoutRequest, Response, Grid (AG-Grid), List, Stack, Timeline, Stat, Fieldset, Fab

3. Examples with "Superpowers"

A. The Declarative API Flow (Request & Response)

Instead of manually managing loading and error flags, use these together to handle data fetching elegantly.

javascript
// 1. Define the request (it tracks dependencies automatically)
 const userProfile = Request(
   () => \`https://api.example.com/user/\${userId()}\`
 );
@@ -11,7 +11,7 @@ import{_ as i,o as a,c as t,ae as e}from"./chunks/framework.C8AWLET_.js";const c
       P(data.email)
     ])
   )
-]);

B. Smart Inputs & Autocomplete

Native inputs are boring. SigPro UI inputs handle labels, icons, password toggles, and validation states out of the box.

javascript
const searchQuery = $("");
+]);

B. Smart Inputs & Autocomplete

SigPro UI inputs handle labels, icons, password toggles, and validation states out of the box using DaisyUI v5 classes.

javascript
const searchQuery = $("");
 
 Autocomplete({
   label: "Find a Country",
@@ -19,13 +19,13 @@ import{_ as i,o as a,c as t,ae as e}from"./chunks/framework.C8AWLET_.js";const c
   options: ["Spain", "France", "Germany", "Italy", "Portugal"],
   $value: searchQuery,
   onSelect: (val) => console.log("Selected:", val)
-});

C. The Reactive Datepicker

Handles single dates or ranges with a clean, reactive interface.

javascript
const myDate = $(""); // or { start: "", end: "" } for range
+});

C. The Reactive Datepicker

Handles single dates or ranges with a clean, reactive interface that automatically syncs with your signals.

javascript
const myDate = $(""); // or { start: "", end: "" } for range
 
 Datepicker({
   label: "Select Expiry Date",
   $value: myDate,
-  range: false // Set to true for range selection
-});

D. Imperative Toasts & Modals

Sometimes you just need to trigger a message without cluttering your template.

javascript
// Show a notification from anywhere in your logic
+  range: false
+});

D. Imperative Toasts & Modals

Trigger complex UI elements from your logic. These components use $.mount internally to ensure they are properly cleaned up from memory after they close.

javascript
// Show a notification (Self-destroying after 3s)
 Toast("Settings saved successfully!", "alert-success", 3000);
 
 // Control a modal with a simple signal
@@ -40,5 +40,5 @@ import{_ as i,o as a,c as t,ae as e}from"./chunks/framework.C8AWLET_.js";const c
 }, "This action cannot be undone.");

4. Internationalization (i18n)

The UI library comes with a built-in locale system. It currently supports es and en.

javascript
// Set the global UI language
 SetLocale("en");
 
-// Access translated strings in your own components
-const t = tt("confirm"); // Returns a signal that tracks the current locale

5. Best Practices

  • Use $ for Reactivity: If a property starts with $, it expects a Signal (e.g., $value: mySignal).
  • Key your Lists: When using For, always provide a keyFn to ensure high-performance DOM reconciliation.
  • Cleanups: If you build custom components that use setInterval or observers, add them to the element's _cleanups Set.
`,29)])])}const E=i(n,[["render",l]]);export{c as __pageData,E as default}; +// Access translated strings (Returns a signal that tracks the current locale) +const t = tt("confirm");

5. Best Practices

  • Use $ for Reactivity: If a property starts with $, the component expects a Signal (e.g., $value: mySignal).
  • Automatic Cleaning: You don't need to manually destroy these components if they are inside a $.If or $.For. SigPro's core will "sweep" their internal watchers automatically.
  • Manual Cleanups: If you build custom components using setInterval or third-party observers, always add the stop functions to the element's ._cleanups Set.
`,33)])])}const E=i(n,[["render",l]]);export{c as __pageData,E as default}; diff --git a/docs/assets/ui_quick.md.Bzj-nQ2u.lean.js b/docs/assets/ui_quick.md.CsppjR8J.lean.js similarity index 52% rename from docs/assets/ui_quick.md.Bzj-nQ2u.lean.js rename to docs/assets/ui_quick.md.CsppjR8J.lean.js index 1f4d722..d5bccf8 100644 --- a/docs/assets/ui_quick.md.Bzj-nQ2u.lean.js +++ b/docs/assets/ui_quick.md.CsppjR8J.lean.js @@ -1 +1 @@ -import{_ as i,o as a,c as t,ae as e}from"./chunks/framework.C8AWLET_.js";const c=JSON.parse('{"title":"🧩 UI Components (WIP)","description":"","frontmatter":{},"headers":[],"relativePath":"ui/quick.md","filePath":"ui/quick.md"}'),n={name:"ui/quick.md"};function l(h,s,p,o,k,r){return a(),t("div",null,[...s[0]||(s[0]=[e("",29)])])}const E=i(n,[["render",l]]);export{c as __pageData,E as default}; +import{_ as i,o as a,c as e,ae as t}from"./chunks/framework.C8AWLET_.js";const c=JSON.parse('{"title":"🧩 UI Components (WIP)","description":"","frontmatter":{},"headers":[],"relativePath":"ui/quick.md","filePath":"ui/quick.md"}'),n={name:"ui/quick.md"};function l(h,s,p,o,r,k){return a(),e("div",null,[...s[0]||(s[0]=[t("",33)])])}const E=i(n,[["render",l]]);export{c as __pageData,E as default}; diff --git a/docs/examples.html b/docs/examples.html index f604105..16424f8 100644 --- a/docs/examples.html +++ b/docs/examples.html @@ -19,7 +19,7 @@ -
Skip to content

Live Playground

Experience SigPro's fine-grained reactivity in real-time. Feel free to tweak the signal values in the editor!

```

2. Best Practices for Documentation

  • Tab Selection: You can control which tabs are active by default by changing the URL segment after /embedded/.
    • js,result: Shows the logic and the output.
    • html,js,result: Shows the base structure, the logic, and the output.
  • Height Management: For complex Store examples, increase the height attribute to 500 or 600 so the code is readable without internal scrolling.
  • Responsive Width: Keeping width="100%" ensures the fiddle scales correctly on tablets and mobile devices.

3. Advanced: The "Fiddle" Component (Optional)

If you plan to have 10+ examples, you can create a global Vue component in VitePress. This keeps your Markdown files clean and allows you to change the theme or default height for all fiddles at once.

Create .vitepress/theme/components/Fiddle.vue:

vue
<template>
+    
Skip to content

Live Playground

Experience SigPro's fine-grained reactivity in real-time. Feel free to tweak the signal values in the editor!

```

2. Best Practices for Documentation

  • Tab Selection: You can control which tabs are active by default by changing the URL segment after /embedded/.
    • js,result: Shows the logic and the output.
    • html,js,result: Shows the base structure, the logic, and the output.
  • Height Management: For complex Store examples, increase the height attribute to 500 or 600 so the code is readable without internal scrolling.
  • Responsive Width: Keeping width="100%" ensures the fiddle scales correctly on tablets and mobile devices.

3. Advanced: The "Fiddle" Component (Optional)

If you plan to have 10+ examples, you can create a global Vue component in VitePress. This keeps your Markdown files clean and allows you to change the theme or default height for all fiddles at once.

Create .vitepress/theme/components/Fiddle.vue:

vue
<template>
   <div class="fiddle-wrapper" style="margin: 20px 0;">
     <iframe 
       width="100%" 
@@ -39,7 +39,7 @@
 })
 </script>

Usage in Markdown:

markdown
Check out this store example:
 <Fiddle id="spwran02/4" height="500" />

Why this is perfect for SigPro:

Because SigPro is zero-dependency and runs directly in the browser, your JSFiddle code will be exactly what the user copies into their own index.html. There is no hidden "build step" confusing the learner.

- + \ No newline at end of file diff --git a/docs/hashmap.json b/docs/hashmap.json index 6bde688..a7acb7c 100644 --- a/docs/hashmap.json +++ b/docs/hashmap.json @@ -1 +1 @@ -{"api_effect.md":"jV8KzXq5","api_html.md":"COPskx0H","api_ignore.md":"CxKek-H-","api_mount.md":"CRwLyxt8","api_quick.md":"4axUqmd3","api_router.md":"Cn98LjXO","api_signal.md":"BmorvARW","api_tags.md":"VliNqepa","api_view.md":"Bv8Rlx9s","examples.md":"Cy97nBRw","index.md":"By6smViD","install.md":"DmlvO98W","ui_quick.md":"Bzj-nQ2u","vite_plugin.md":"CTs8LDIL"} +{"api_html.md":"BPbZMZR1","api_mount.md":"BiKjH18I","api_quick.md":"4axUqmd3","api_router.md":"Cn98LjXO","api_signal.md":"BmorvARW","api_tags.md":"CW_zjfl9","api_watch.md":"D7sOEzCX","examples.md":"Cy97nBRw","index.md":"By6smViD","install.md":"C0utklUK","ui_quick.md":"CsppjR8J","vite_plugin.md":"CTs8LDIL"} diff --git a/docs/index.html b/docs/index.html index 20b2f8d..549ed1d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -20,7 +20,7 @@
Skip to content

SigProAtomic Unified Reactive Engine

High-precision atomic reactivity. No Virtual DOM. No compiler. No dependencies.

SigPro Logo

Redefining Modern Reactivity

SigPro is not just another framework; it is a high-performance engine. While other libraries add layers of abstraction that slow down execution, SigPro returns to the essence of the web, leveraging the power of modern browser engines.

Why SigPro?

⚡️ Surgical DOM Efficiency

Unlike React or Vue, SigPro doesn't compare element trees. When a signal changes, SigPro knows exactly which DOM node depends on it and updates it instantly. It is reactive precision at its finest.

🔌 Modular Plugin System

The core is sacred. Any extra functionality—Routing, UI Helpers, or State Persistence—is integrated through a polymorphic plugin system. Load only what your application truly needs.

💾 Native Persistence

SigPro features first-class support for localStorage. Synchronizing your application state with persistent storage is as simple as providing a key when initializing your Signal.

🚦 Built-in Hash Routing

A robust routing system that supports Native Lazy Loading out of the box. Load your components only when the user navigates to them, keeping initial load times near zero.


The "No-Build" Philosophy

In an ecosystem obsessed with compilers, SigPro bets on standardization. Write code today that will still run 10 years from now, without depending on build tools that will eventually become obsolete.

"The best way to optimize code is to not have to process it at all."


Community & Vision

SigPro is an open-source project focused on simplicity and extreme speed. Designed for developers who love the web platform and hate unnecessary "bloatware".

text
Built with ❤️ by NatxoCC for the Modern Web.
- + \ No newline at end of file diff --git a/docs/install.html b/docs/install.html index ecefeb7..6452e89 100644 --- a/docs/install.html +++ b/docs/install.html @@ -13,20 +13,20 @@ - + -
Skip to content

Installation & Setup

SigPro is designed to be drop-in ready. Whether you are building a complex application with a bundler or a simple reactive widget in a single HTML file, SigPro scales with your needs.

1. Installation

Choose the method that best fits your workflow:

bash
npm install sigpro
bash
pnpm add sigpro
bash
yarn add sigpro
bash
bun add sigpro
html
<script type="module">
+    
Skip to content

Installation & Setup

SigPro is designed to be drop-in ready. Whether you are building a complex application with a bundler or a simple reactive widget in a single HTML file, SigPro scales with your needs.

1. Installation

Choose the method that best fits your workflow:

bash
npm install sigpro
bash
pnpm add sigpro
bash
yarn add sigpro
bash
bun add sigpro
html
<script type="module">
   // Import the core and UI components
   import { $ } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/+esm';
   import { UI } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/ui/+esm';
   
   // Initialize UI components globally
   UI($);
-</script>

2. Quick Start Examples

SigPro uses PascalCase for Tag Helpers (e.g., Div, Button) to provide a clean, component-like syntax without needing JSX.

javascript
// File: App.js
+</script>

2. Quick Start Examples

SigPro uses PascalCase for Tag Helpers (e.g., Div, Button) to provide a clean, component-like syntax without needing JSX.

javascript
// File: App.js
 import { $ } from 'sigpro'; 
 
 export const App = () => {
@@ -71,7 +71,7 @@
   </script>
 </body>
 </html>

3. Global by Design

One of SigPro's core strengths is its Global API, which eliminates "Import Hell".

  • The $ Function: Once loaded, it attaches itself to window.$. It handles state, effects, and DOM mounting.
  • Tag Helpers (PascalCase): Common HTML tags (Div, Span, Button, Input, etc.) are automatically registered in the global scope.
  • Custom Components: We recommend using PascalCase (e.g., UserCard) or prefixes like _Input to keep your code organized and distinguish your logic from standard tags.

4. Why no build step?

Because SigPro uses native ES Modules and standard JavaScript functions to generate the DOM, you don't actually need a compiler like Babel or a transformer for JSX.

  • Development: Just save and refresh. Pure JS, no "transpilation" required.
  • Performance: Extremely lightweight. Use any modern bundler (Vite, esbuild) only when you are ready to minify and tree-shake for production.

5. Why SigPro? (The Competitive Edge)

SigPro stands out by removing the "Build Step" tax and the "Virtual DOM" overhead. It is the closest you can get to writing raw HTML/JS while maintaining modern reactivity.

FeatureSigProSolidJSSvelteReactVue
Bundle Size~2KB~7KB~4KB~40KB+~30KB
DOM StrategyDirect DOMDirect DOMCompiled DOMVirtual DOMVirtual DOM
ReactivityFine-grainedFine-grainedCompiledRe-rendersProxies
Build StepOptionalRequiredRequiredRequiredOptional
Learning CurveMinimalMediumLowHighMedium
InitializationUltra-FastVery FastFastSlowMedium

6. Key Advantages

  • Extreme Performance: No Virtual DOM reconciliation. SigPro updates the specific node or attribute instantly when a Signal changes.
  • Fine-Grained Reactivity: State changes only trigger updates where the data is actually used, not on the entire component.
  • Native Web Standards: Everything is a standard JS function. No custom template syntax to learn.
  • Zero Magic: No hidden compilers. What you write is what runs in the browser.
  • Global by Design: Tag Helpers and the $ function are available globally to eliminate "Import Hell" and keep your code clean.

7. Summary

SigPro isn't just another framework; it's a bridge to the native web. By using standard ES Modules and functional DOM generation, you gain the benefits of a modern library with the weight of a utility script.

Because, in the end... why fight the web when we can embrace it?

- + \ No newline at end of file diff --git a/docs/ui/quick.html b/docs/ui/quick.html index feb48d8..8eed511 100644 --- a/docs/ui/quick.html +++ b/docs/ui/quick.html @@ -13,13 +13,13 @@ - + -
Skip to content

🧩 UI Components (WIP)

Status: Work In Progress. > These are high-level, complex visual components designed to speed up development. They often replace native HTML elements with "superpowered" versions that handle their own internal logic, reactivity, and accessibility.

1. What are UI Components?

Unlike Tag Helpers (which are just functional mirrors of HTML tags), SigPro UI Components are smart abstractions.

  • Stateful: They manage complex internal states (like date ranges, search filtering, or API lifecycles).
  • Reactive: Attributes prefixed with $ are automatically tracked.
  • Self-Cleaning: They automatically use _cleanups to destroy observers or event listeners when removed from the DOM.
  • Themed: Fully compatible with Tailwind CSS and DaisyUI themes.

2. The UI Registry (Available Now)

CategoryComponents
Logic & FlowIf, For, Json
Forms & InputsButton, Input, Select, Autocomplete, Datepicker, Colorpicker, CheckBox, Radio, Range, Rating, Swap
FeedbackAlert, Toast, Modal, Loading, Badge, Tooltip, Indicator
NavigationNavbar, Menu, Drawer, Tabs, Accordion, Dropdown
Data & LayoutRequest, Response, Grid (AG-Grid), List, Stack, Timeline, Stat, Fieldset, Fab

3. Examples with "Superpowers"

A. The Declarative API Flow (Request & Response)

Instead of manually managing loading and error flags, use these two together to handle data fetching elegantly.

javascript
// 1. Define the request (it tracks dependencies automatically)
+    
Skip to content

🧩 UI Components (WIP)

Status: Work In Progress. > These are high-level, complex visual components designed to speed up development. They replace native HTML elements with "superpowered" versions that handle their own internal logic, reactivity, and professional styling.

⚠️ Prerequisites

To ensure all components render correctly with their reactive themes and states, your project must have the following versions installed:

  • Tailwind CSS v4+: For the new engine performance and modern CSS variables.
  • DaisyUI v5+: Required for the updated theme-selectors and improved component classes used in the SigPro UI library.

1. What are UI Components?

Unlike Tag Helpers (which are just functional mirrors of HTML tags), SigPro UI Components are smart abstractions:

  • Stateful: They manage complex internal states (like date ranges, search filtering, or API lifecycles).
  • Reactive: Attributes prefixed with $ are automatically tracked via $.watch.
  • Self-Sane: They automatically use ._cleanups to destroy observers or event listeners when removed from the DOM.
  • Themed: Fully compatible with the DaisyUI v5 theme system and Tailwind v4 utility classes.

2. The UI Registry (Available Now)

CategoryComponents
Forms & InputsButton, Input, Select, Autocomplete, Datepicker, Colorpicker, CheckBox, Radio, Range, Rating, Swap
FeedbackAlert, Toast, Modal, Loading, Badge, Tooltip, Indicator
NavigationNavbar, Menu, Drawer, Tabs, Accordion, Dropdown
Data & LayoutRequest, Response, Grid (AG-Grid), List, Stack, Timeline, Stat, Fieldset, Fab

3. Examples with "Superpowers"

A. The Declarative API Flow (Request & Response)

Instead of manually managing loading and error flags, use these together to handle data fetching elegantly.

javascript
// 1. Define the request (it tracks dependencies automatically)
 const userProfile = Request(
   () => `https://api.example.com/user/${userId()}`
 );
@@ -32,7 +32,7 @@
       P(data.email)
     ])
   )
-]);

B. Smart Inputs & Autocomplete

Native inputs are boring. SigPro UI inputs handle labels, icons, password toggles, and validation states out of the box.

javascript
const searchQuery = $("");
+]);

B. Smart Inputs & Autocomplete

SigPro UI inputs handle labels, icons, password toggles, and validation states out of the box using DaisyUI v5 classes.

javascript
const searchQuery = $("");
 
 Autocomplete({
   label: "Find a Country",
@@ -40,13 +40,13 @@
   options: ["Spain", "France", "Germany", "Italy", "Portugal"],
   $value: searchQuery,
   onSelect: (val) => console.log("Selected:", val)
-});

C. The Reactive Datepicker

Handles single dates or ranges with a clean, reactive interface.

javascript
const myDate = $(""); // or { start: "", end: "" } for range
+});

C. The Reactive Datepicker

Handles single dates or ranges with a clean, reactive interface that automatically syncs with your signals.

javascript
const myDate = $(""); // or { start: "", end: "" } for range
 
 Datepicker({
   label: "Select Expiry Date",
   $value: myDate,
-  range: false // Set to true for range selection
-});

D. Imperative Toasts & Modals

Sometimes you just need to trigger a message without cluttering your template.

javascript
// Show a notification from anywhere in your logic
+  range: false
+});

D. Imperative Toasts & Modals

Trigger complex UI elements from your logic. These components use $.mount internally to ensure they are properly cleaned up from memory after they close.

javascript
// Show a notification (Self-destroying after 3s)
 Toast("Settings saved successfully!", "alert-success", 3000);
 
 // Control a modal with a simple signal
@@ -61,9 +61,9 @@
 }, "This action cannot be undone.");

4. Internationalization (i18n)

The UI library comes with a built-in locale system. It currently supports es and en.

javascript
// Set the global UI language
 SetLocale("en");
 
-// Access translated strings in your own components
-const t = tt("confirm"); // Returns a signal that tracks the current locale

5. Best Practices

  • Use $ for Reactivity: If a property starts with $, it expects a Signal (e.g., $value: mySignal).
  • Key your Lists: When using For, always provide a keyFn to ensure high-performance DOM reconciliation.
  • Cleanups: If you build custom components that use setInterval or observers, add them to the element's _cleanups Set.
- +// Access translated strings (Returns a signal that tracks the current locale) +const t = tt("confirm");

5. Best Practices

  • Use $ for Reactivity: If a property starts with $, the component expects a Signal (e.g., $value: mySignal).
  • Automatic Cleaning: You don't need to manually destroy these components if they are inside a $.If or $.For. SigPro's core will "sweep" their internal watchers automatically.
  • Manual Cleanups: If you build custom components using setInterval or third-party observers, always add the stop functions to the element's ._cleanups Set.
+ \ No newline at end of file diff --git a/docs/vite/plugin.html b/docs/vite/plugin.html index 88d938d..95d297b 100644 --- a/docs/vite/plugin.html +++ b/docs/vite/plugin.html @@ -19,7 +19,7 @@ -
Skip to content

Vite Plugin: File-based Routing

The sigproRouter plugin for Vite automates route generation by scanning your pages directory. It creates a virtual module that you can import directly into your code, eliminating the need to maintain a manual routes array.

1. Project Structure

To use the plugin, organize your files within the src/pages directory. The folder hierarchy directly determines your application's URL structure. SigPro uses brackets [param] for dynamic segments.

text
my-sigpro-app/
+    
Skip to content

Vite Plugin: File-based Routing

The sigproRouter plugin for Vite automates route generation by scanning your pages directory. It creates a virtual module that you can import directly into your code, eliminating the need to maintain a manual routes array.

1. Project Structure

To use the plugin, organize your files within the src/pages directory. The folder hierarchy directly determines your application's URL structure. SigPro uses brackets [param] for dynamic segments.

text
my-sigpro-app/
 ├── src/
 │   ├── pages/
 │   │   ├── index.js          →  #/
@@ -68,7 +68,7 @@
   { path: '/users/:id', component: () => import('/src/pages/users/[id].js') },
   // ...
 ];

Because it uses dynamic import(), Vite automatically performs Code Splitting, meaning each page is its own small JS file that only loads when the user navigates to it.

- + \ No newline at end of file diff --git a/package.json b/package.json index 96264fa..216bf00 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sigpro", - "version": "1.1.4", + "version": "1.1.5", "type": "module", "license": "MIT", "exports": { diff --git a/sigpro/sigpro.js b/sigpro/sigpro.js index b11e200..ea724eb 100644 --- a/sigpro/sigpro.js +++ b/sigpro/sigpro.js @@ -1,5 +1,5 @@ /** - * SigPro Core + * SigPro Core */ (() => { let activeEffect = null; @@ -42,14 +42,11 @@ const $ = (initial, key = null) => { if (typeof initial === "function") { const subs = new Set(); - let cached; - let dirty = true; - + let cached, dirty = true; const effect = () => { if (effect._deleted) return; effect._deps.forEach((s) => s.delete(effect)); effect._deps.clear(); - const prev = activeEffect; activeEffect = effect; try { @@ -59,57 +56,34 @@ dirty = false; trigger(subs); } - } finally { - activeEffect = prev; - } + } finally { activeEffect = prev; } }; - effect._deps = new Set(); effect._isComputed = true; effect._subs = subs; effect._deleted = false; effect.markDirty = () => (dirty = true); - effect.stop = () => { effect._deleted = true; effect._deps.forEach((s) => s.delete(effect)); - effect._deps.clear(); subs.clear(); }; - if (currentOwner) currentOwner.cleanups.add(effect.stop); - - return () => { - if (dirty) effect(); - track(subs); - return cached; - }; + return () => { if (dirty) effect(); track(subs); return cached; }; } let value = initial; - if (key) { const saved = localStorage.getItem(key); - if (saved !== null) { - try { - value = JSON.parse(saved); - } catch { - value = saved; - } - } + if (saved !== null) try { value = JSON.parse(saved); } catch { value = saved; } } - const subs = new Set(); - return (...args) => { if (args.length) { const next = typeof args[0] === "function" ? args[0](value) : args[0]; if (!Object.is(value, next)) { value = next; - if (key) { - localStorage.setItem(key, JSON.stringify(value)); - } - + if (key) localStorage.setItem(key, JSON.stringify(value)); trigger(subs); } } @@ -118,52 +92,57 @@ }; }; - $.effect = (fn) => { - const owner = currentOwner; + $.watch = (target, fn) => { + const isExplicit = Array.isArray(target); + const callback = isExplicit ? fn : target; + const depsInput = isExplicit ? target : null; - const effect = () => { - if (effect._deleted) return; - effect._deps.forEach((s) => s.delete(effect)); - effect._deps.clear(); - effect._cleanups.forEach((c) => c()); - effect._cleanups.clear(); + if (typeof callback !== "function") return () => {}; + + const owner = currentOwner; + const runner = () => { + if (runner._deleted) return; + runner._deps.forEach((s) => s.delete(runner)); + runner._deps.clear(); + runner._cleanups.forEach((c) => c()); + runner._cleanups.clear(); const prevEffect = activeEffect; const prevOwner = currentOwner; - activeEffect = effect; - currentOwner = { cleanups: effect._cleanups }; - effect.depth = prevEffect ? prevEffect.depth + 1 : 0; + activeEffect = runner; + currentOwner = { cleanups: runner._cleanups }; + runner.depth = prevEffect ? prevEffect.depth + 1 : 0; try { - fn(); + if (isExplicit) { + activeEffect = null; + callback(); + activeEffect = runner; + depsInput.forEach(d => typeof d === "function" && d()); + } else { + callback(); + } } finally { activeEffect = prevEffect; currentOwner = prevOwner; } }; - effect._deps = new Set(); - effect._cleanups = new Set(); - effect._deleted = false; - - effect.stop = () => { - if (effect._deleted) return; - effect._deleted = true; - effectQueue.delete(effect); - effect._deps.forEach((s) => s.delete(effect)); - effect._deps.clear(); - effect._cleanups.forEach((c) => c()); - effect._cleanups.clear(); - - if (owner) { - owner.cleanups.delete(effect.stop); - } + runner._deps = new Set(); + runner._cleanups = new Set(); + runner._deleted = false; + runner.stop = () => { + if (runner._deleted) return; + runner._deleted = true; + effectQueue.delete(runner); + runner._deps.forEach((s) => s.delete(runner)); + runner._cleanups.forEach((c) => c()); + if (owner) owner.cleanups.delete(runner.stop); }; - if (owner) owner.cleanups.add(effect.stop); - - effect(); - return effect.stop; + if (owner) owner.cleanups.add(runner.stop); + runner(); + return runner.stop; }; const sweep = (node) => { @@ -174,7 +153,7 @@ node.childNodes?.forEach(sweep); }; - $.view = (fn) => { + const _view = (fn) => { const cleanups = new Set(); const prev = currentOwner; const container = document.createElement("div"); @@ -191,9 +170,7 @@ else container.appendChild(n instanceof Node ? n : document.createTextNode(String(n))); }; process(res); - } finally { - currentOwner = prev; - } + } finally { currentOwner = prev; } return { _isRuntime: true, container, @@ -207,8 +184,7 @@ $.html = (tag, props = {}, content = []) => { if (props instanceof Node || Array.isArray(props) || typeof props !== "object") { - content = props; - props = {}; + content = props; props = {}; } const el = document.createElement(tag); el._cleanups = new Set(); @@ -216,43 +192,27 @@ for (let [k, v] of Object.entries(props)) { if (k.startsWith("on")) { const name = k.slice(2).toLowerCase().split(".")[0]; - const mods = k.slice(2).toLowerCase().split(".").slice(1); - const handler = (e) => { - if (mods.includes("prevent")) e.preventDefault(); - if (mods.includes("stop")) e.stopPropagation(); - v(e); - }; - el.addEventListener(name, handler, { once: mods.includes("once") }); + const handler = (e) => v(e); + el.addEventListener(name, handler); el._cleanups.add(() => el.removeEventListener(name, handler)); } else if (k.startsWith("$")) { const attr = k.slice(1); - const stopAttr = $.effect(() => { + el._cleanups.add($.watch(() => { const val = typeof v === "function" ? v() : v; - if (el[attr] === val) return; - if (attr === "value" || attr === "checked") el[attr] = val; - else if (typeof val === "boolean") el.toggleAttribute(attr, val); - else val == null ? el.removeAttribute(attr) : el.setAttribute(attr, val); - }); - el._cleanups.add(stopAttr); - + if (el[attr] !== val) el[attr] = val; + })); if (typeof v === "function") { const evt = attr === "checked" ? "change" : "input"; - const handler = (e) => v(e.target[attr]); - el.addEventListener(evt, handler); - el._cleanups.add(() => el.removeEventListener(evt, handler)); + el.addEventListener(evt, (e) => v(e.target[attr])); } } else if (typeof v === "function") { - const stopAttr = $.effect(() => { + el._cleanups.add($.watch(() => { const val = v(); - if (k === "class" || k === "className") el.className = val || ""; - else if (typeof val === "boolean") el.toggleAttribute(k, val); + if (k === "class") el.className = val || ""; else val == null ? el.removeAttribute(k) : el.setAttribute(k, val); - }); - el._cleanups.add(stopAttr); + })); } else { - if (k === "class" || k === "className") el.className = v || ""; - else if (typeof v === "boolean") el.toggleAttribute(k, v); - else v == null ? el.removeAttribute(k) : el.setAttribute(k, v); + el.setAttribute(k, v); } } @@ -262,74 +222,91 @@ const marker = document.createTextNode(""); el.appendChild(marker); let nodes = []; - const stopList = $.effect(() => { + el._cleanups.add($.watch(() => { const res = c(); const next = (Array.isArray(res) ? res : [res]).map((i) => - i?._isRuntime ? i.container : i instanceof Node ? i : document.createTextNode(i ?? ""), + i?._isRuntime ? i.container : i instanceof Node ? i : document.createTextNode(i ?? "") ); - nodes.forEach((n) => { - sweep(n); - n.remove(); - }); + nodes.forEach((n) => { sweep(n); n.remove(); }); next.forEach((n) => marker.parentNode?.insertBefore(n, marker)); nodes = next; - }); - el._cleanups.add(stopList); + })); } else el.appendChild(c instanceof Node ? c : document.createTextNode(c ?? "")); }; append(content); return el; }; - $.ignore = (fn) => { - const prev = activeEffect; - activeEffect = null; - try { - return fn(); - } finally { - activeEffect = prev; - } + $.if = (condition, thenVal, otherwiseVal = null) => { + const marker = document.createTextNode(""); + const container = $.html("div", { style: "display:contents" }, [marker]); + let current = null, last = null; + $.watch(() => { + const state = !!(typeof condition === "function" ? condition() : condition); + if (state !== last) { + last = state; + if (current) current.destroy(); + const branch = state ? thenVal : otherwiseVal; + if (branch) { + current = _view(() => typeof branch === "function" ? branch() : branch); + container.insertBefore(current.container, marker); + } + } + }); + return container; + }; + + $.for = (source, render, keyFn) => { + const marker = document.createTextNode(""); + const container = $.html("div", { style: "display:contents" }, [marker]); + const cache = new Map(); + $.watch(() => { + const items = (typeof source === "function" ? source() : source) || []; + const newKeys = new Set(); + items.forEach((item, index) => { + const key = keyFn(item, index); + newKeys.add(key); + let run = cache.get(key); + if (!run) { + run = _view(() => render(item, index)); + cache.set(key, run); + } + container.insertBefore(run.container, marker); + }); + cache.forEach((run, key) => { + if (!newKeys.has(key)) { run.destroy(); cache.delete(key); } + }); + }); + return container; }; $.router = (routes) => { const sPath = $(window.location.hash.replace(/^#/, "") || "/"); window.addEventListener("hashchange", () => sPath(window.location.hash.replace(/^#/, "") || "/")); - const outlet = Div({ class: "router-outlet" }); let current = null; - $.effect(() => { - const path = sPath(); + $.watch([sPath], () => { if (current) current.destroy(); - outlet.innerHTML = ""; - - const parts = path.split("/").filter(Boolean); - const route = - routes.find((r) => { - const rp = r.path.split("/").filter(Boolean); - return rp.length === parts.length && rp.every((p, i) => p.startsWith(":") || p === parts[i]); - }) || routes.find((r) => r.path === "*"); + const path = sPath(); + const route = routes.find(r => { + const rp = r.path.split("/").filter(Boolean); + const pp = path.split("/").filter(Boolean); + return rp.length === pp.length && rp.every((p, i) => p.startsWith(":") || p === pp[i]); + }) || routes.find(r => r.path === "*"); if (route) { const params = {}; - route.path - .split("/") - .filter(Boolean) - .forEach((p, i) => { - if (p.startsWith(":")) params[p.slice(1)] = parts[i]; - }); - - current = $.ignore(() => - $.view(() => { - const res = route.component(params); - return typeof res === "function" ? res() : res; - }), - ); - + route.path.split("/").filter(Boolean).forEach((p, i) => { + if (p.startsWith(":")) params[p.slice(1)] = path.split("/").filter(Boolean)[i]; + }); + current = _view(() => { + const res = route.component(params); + return typeof res === "function" ? res() : res; + }); outlet.appendChild(current.container); } }); - return outlet; }; @@ -339,21 +316,17 @@ const el = typeof target === "string" ? document.querySelector(target) : target; if (!el) return; if (MOUNTED_NODES.has(el)) MOUNTED_NODES.get(el).destroy(); - const instance = $.view(typeof component === "function" ? component : () => component); + const instance = _view(typeof component === "function" ? component : () => component); el.replaceChildren(instance.container); MOUNTED_NODES.set(el, instance); return instance; }; - const tags = - `div span p h1 h2 h3 h4 h5 h6 br hr section article aside nav main header footer address ul ol li dl dt dd a em strong small i b u mark time sub sup pre code blockquote details summary dialog form label input textarea select button option fieldset legend table thead tbody tfoot tr th td caption img video audio canvas svg iframe picture source progress meter`.split( - /\s+/, - ); - + const tags = `div span p h1 h2 h3 h4 h5 h6 br hr section article aside nav main header footer address ul ol li dl dt dd a em strong small i b u mark time sub sup pre code blockquote details summary dialog form label input textarea select button option fieldset legend table thead tbody tfoot tr th td caption img video audio canvas svg iframe picture source progress meter`.split(/\s+/); tags.forEach((t) => { window[t.charAt(0).toUpperCase() + t.slice(1)] = (p, c) => $.html(t, p, c); }); window.$ = $; })(); -export const { $ } = window; +export const { $ } = window; \ No newline at end of file diff --git a/sigpro/sigpro.min.js b/sigpro/sigpro.min.js index 23f3c0c..4a44032 100644 --- a/sigpro/sigpro.min.js +++ b/sigpro/sigpro.min.js @@ -1 +1 @@ -(()=>{let e=null,t=null;const n=new Set;let o=!1;const s=new WeakMap,r=()=>{if(!o){for(o=!0;n.size>0;){const e=Array.from(n).sort(((e,t)=>(e.depth||0)-(t.depth||0)));n.clear();for(const t of e)t._deleted||t()}o=!1}},a=t=>{e&&!e._deleted&&(t.add(e),e._deps.add(t))},c=t=>{for(const o of t)o===e||o._deleted||(o._isComputed?(o.markDirty(),o._subs&&c(o._subs)):n.add(o));o||queueMicrotask(r)},l=(n,o=null)=>{if("function"==typeof n){const o=new Set;let s,r=!0;const l=()=>{if(l._deleted)return;l._deps.forEach((e=>e.delete(l))),l._deps.clear();const t=e;e=l;try{const a=n();Object.is(s,a)&&!r||(s=a,r=!1,c(o))}finally{e=t}};return l._deps=new Set,l._isComputed=!0,l._subs=o,l._deleted=!1,l.markDirty=()=>r=!0,l.stop=()=>{l._deleted=!0,l._deps.forEach((e=>e.delete(l))),l._deps.clear(),o.clear()},t&&t.cleanups.add(l.stop),()=>(r&&l(),a(o),s)}let s=n;if(o){const e=localStorage.getItem(o);if(null!==e)try{s=JSON.parse(e)}catch{s=e}}const r=new Set;return(...e)=>{if(e.length){const t="function"==typeof e[0]?e[0](s):e[0];Object.is(s,t)||(s=t,o&&localStorage.setItem(o,JSON.stringify(s)),c(r))}return a(r),s}};l.effect=o=>{const s=t,r=()=>{if(r._deleted)return;r._deps.forEach((e=>e.delete(r))),r._deps.clear(),r._cleanups.forEach((e=>e())),r._cleanups.clear();const n=e,s=t;e=r,t={cleanups:r._cleanups},r.depth=n?n.depth+1:0;try{o()}finally{e=n,t=s}};return r._deps=new Set,r._cleanups=new Set,r._deleted=!1,r.stop=()=>{r._deleted||(r._deleted=!0,n.delete(r),r._deps.forEach((e=>e.delete(r))),r._deps.clear(),r._cleanups.forEach((e=>e())),r._cleanups.clear(),s&&s.cleanups.delete(r.stop))},s&&s.cleanups.add(r.stop),r(),r.stop};const i=e=>{e._cleanups&&(e._cleanups.forEach((e=>e())),e._cleanups.clear()),e.childNodes?.forEach(i)};l.view=e=>{const n=new Set,o=t,s=document.createElement("div");s.style.display="contents",t={cleanups:n};try{const r=e({onCleanup:e=>n.add(e)}),a=e=>{e&&(e._isRuntime?(n.add(e.destroy),s.appendChild(e.container)):Array.isArray(e)?e.forEach(a):s.appendChild(e instanceof Node?e:document.createTextNode(String(e))))};a(r)}finally{t=o}return{_isRuntime:!0,container:s,destroy:()=>{n.forEach((e=>e())),i(s),s.remove()}}},l.html=(e,t={},n=[])=>{(t instanceof Node||Array.isArray(t)||"object"!=typeof t)&&(n=t,t={});const o=document.createElement(e);o._cleanups=new Set;for(let[e,n]of Object.entries(t))if(e.startsWith("on")){const t=e.slice(2).toLowerCase().split(".")[0],s=e.slice(2).toLowerCase().split(".").slice(1),r=e=>{s.includes("prevent")&&e.preventDefault(),s.includes("stop")&&e.stopPropagation(),n(e)};o.addEventListener(t,r,{once:s.includes("once")}),o._cleanups.add((()=>o.removeEventListener(t,r)))}else if(e.startsWith("$")){const t=e.slice(1),s=l.effect((()=>{const e="function"==typeof n?n():n;o[t]!==e&&("value"===t||"checked"===t?o[t]=e:"boolean"==typeof e?o.toggleAttribute(t,e):null==e?o.removeAttribute(t):o.setAttribute(t,e))}));if(o._cleanups.add(s),"function"==typeof n){const e="checked"===t?"change":"input",s=e=>n(e.target[t]);o.addEventListener(e,s),o._cleanups.add((()=>o.removeEventListener(e,s)))}}else if("function"==typeof n){const t=l.effect((()=>{const t=n();"class"===e||"className"===e?o.className=t||"":"boolean"==typeof t?o.toggleAttribute(e,t):null==t?o.removeAttribute(e):o.setAttribute(e,t)}));o._cleanups.add(t)}else"class"===e||"className"===e?o.className=n||"":"boolean"==typeof n?o.toggleAttribute(e,n):null==n?o.removeAttribute(e):o.setAttribute(e,n);const s=e=>{if(Array.isArray(e))return e.forEach(s);if("function"==typeof e){const t=document.createTextNode("");o.appendChild(t);let n=[];const s=l.effect((()=>{const o=e(),s=(Array.isArray(o)?o:[o]).map((e=>e?._isRuntime?e.container:e instanceof Node?e:document.createTextNode(e??"")));n.forEach((e=>{i(e),e.remove()})),s.forEach((e=>t.parentNode?.insertBefore(e,t))),n=s}));o._cleanups.add(s)}else o.appendChild(e instanceof Node?e:document.createTextNode(e??""))};return s(n),o},l.ignore=t=>{const n=e;e=null;try{return t()}finally{e=n}},l.router=e=>{const t=l(window.location.hash.replace(/^#/,"")||"/");window.addEventListener("hashchange",(()=>t(window.location.hash.replace(/^#/,"")||"/")));const n=Div({class:"router-outlet"});let o=null;return l.effect((()=>{const s=t();o&&o.destroy(),n.innerHTML="";const r=s.split("/").filter(Boolean),a=e.find((e=>{const t=e.path.split("/").filter(Boolean);return t.length===r.length&&t.every(((e,t)=>e.startsWith(":")||e===r[t]))}))||e.find((e=>"*"===e.path));if(a){const e={};a.path.split("/").filter(Boolean).forEach(((t,n)=>{t.startsWith(":")&&(e[t.slice(1)]=r[n])})),o=l.ignore((()=>l.view((()=>{const t=a.component(e);return"function"==typeof t?t():t})))),n.appendChild(o.container)}})),n},l.go=e=>window.location.hash=e.replace(/^#?\/?/,"#/"),l.mount=(e,t)=>{const n="string"==typeof t?document.querySelector(t):t;if(!n)return;s.has(n)&&s.get(n).destroy();const o=l.view("function"==typeof e?e:()=>e);return n.replaceChildren(o.container),s.set(n,o),o};"div span p h1 h2 h3 h4 h5 h6 br hr section article aside nav main header footer address ul ol li dl dt dd a em strong small i b u mark time sub sup pre code blockquote details summary dialog form label input textarea select button option fieldset legend table thead tbody tfoot tr th td caption img video audio canvas svg iframe picture source progress meter".split(/\s+/).forEach((e=>{window[e.charAt(0).toUpperCase()+e.slice(1)]=(t,n)=>l.html(e,t,n)})),window.$=l})();export const{$:$}=window; \ No newline at end of file +(()=>{let e=null,t=null;const n=new Set;let o=!1;const r=new WeakMap,s=()=>{if(!o){for(o=!0;n.size>0;){const e=Array.from(n).sort(((e,t)=>(e.depth||0)-(t.depth||0)));n.clear();for(const t of e)t._deleted||t()}o=!1}},a=t=>{e&&!e._deleted&&(t.add(e),e._deps.add(t))},c=t=>{for(const o of t)o===e||o._deleted||(o._isComputed?(o.markDirty(),o._subs&&c(o._subs)):n.add(o));o||queueMicrotask(s)},l=(n,o=null)=>{if("function"==typeof n){const o=new Set;let r,s=!0;const l=()=>{if(l._deleted)return;l._deps.forEach((e=>e.delete(l))),l._deps.clear();const t=e;e=l;try{const a=n();Object.is(r,a)&&!s||(r=a,s=!1,c(o))}finally{e=t}};return l._deps=new Set,l._isComputed=!0,l._subs=o,l._deleted=!1,l.markDirty=()=>s=!0,l.stop=()=>{l._deleted=!0,l._deps.forEach((e=>e.delete(l))),o.clear()},t&&t.cleanups.add(l.stop),()=>(s&&l(),a(o),r)}let r=n;if(o){const e=localStorage.getItem(o);if(null!==e)try{r=JSON.parse(e)}catch{r=e}}const s=new Set;return(...e)=>{if(e.length){const t="function"==typeof e[0]?e[0](r):e[0];Object.is(r,t)||(r=t,o&&localStorage.setItem(o,JSON.stringify(r)),c(s))}return a(s),r}};l.watch=(o,r)=>{const s=Array.isArray(o),a=s?r:o,c=s?o:null;if("function"!=typeof a)return()=>{};const l=t,d=()=>{if(d._deleted)return;d._deps.forEach((e=>e.delete(d))),d._deps.clear(),d._cleanups.forEach((e=>e())),d._cleanups.clear();const n=e,o=t;e=d,t={cleanups:d._cleanups},d.depth=n?n.depth+1:0;try{s?(e=null,a(),e=d,c.forEach((e=>"function"==typeof e&&e()))):a()}finally{e=n,t=o}};return d._deps=new Set,d._cleanups=new Set,d._deleted=!1,d.stop=()=>{d._deleted||(d._deleted=!0,n.delete(d),d._deps.forEach((e=>e.delete(d))),d._cleanups.forEach((e=>e())),l&&l.cleanups.delete(d.stop))},l&&l.cleanups.add(d.stop),d(),d.stop};const d=e=>{e._cleanups&&(e._cleanups.forEach((e=>e())),e._cleanups.clear()),e.childNodes?.forEach(d)},i=e=>{const n=new Set,o=t,r=document.createElement("div");r.style.display="contents",t={cleanups:n};try{const s=e({onCleanup:e=>n.add(e)}),a=e=>{e&&(e._isRuntime?(n.add(e.destroy),r.appendChild(e.container)):Array.isArray(e)?e.forEach(a):r.appendChild(e instanceof Node?e:document.createTextNode(String(e))))};a(s)}finally{t=o}return{_isRuntime:!0,container:r,destroy:()=>{n.forEach((e=>e())),d(r),r.remove()}}};l.html=(e,t={},n=[])=>{(t instanceof Node||Array.isArray(t)||"object"!=typeof t)&&(n=t,t={});const o=document.createElement(e);o._cleanups=new Set;for(let[e,n]of Object.entries(t))if(e.startsWith("on")){const t=e.slice(2).toLowerCase().split(".")[0],r=e=>n(e);o.addEventListener(t,r),o._cleanups.add((()=>o.removeEventListener(t,r)))}else if(e.startsWith("$")){const t=e.slice(1);if(o._cleanups.add(l.watch((()=>{const e="function"==typeof n?n():n;o[t]!==e&&(o[t]=e)}))),"function"==typeof n){const e="checked"===t?"change":"input";o.addEventListener(e,(e=>n(e.target[t])))}}else"function"==typeof n?o._cleanups.add(l.watch((()=>{const t=n();"class"===e?o.className=t||"":null==t?o.removeAttribute(e):o.setAttribute(e,t)}))):o.setAttribute(e,n);const r=e=>{if(Array.isArray(e))return e.forEach(r);if("function"==typeof e){const t=document.createTextNode("");o.appendChild(t);let n=[];o._cleanups.add(l.watch((()=>{const o=e(),r=(Array.isArray(o)?o:[o]).map((e=>e?._isRuntime?e.container:e instanceof Node?e:document.createTextNode(e??"")));n.forEach((e=>{d(e),e.remove()})),r.forEach((e=>t.parentNode?.insertBefore(e,t))),n=r})))}else o.appendChild(e instanceof Node?e:document.createTextNode(e??""))};return r(n),o},l.if=(e,t,n=null)=>{const o=document.createTextNode(""),r=l.html("div",{style:"display:contents"},[o]);let s=null,a=null;return l.watch((()=>{const c=!!("function"==typeof e?e():e);if(c!==a){a=c,s&&s.destroy();const e=c?t:n;e&&(s=i((()=>"function"==typeof e?e():e)),r.insertBefore(s.container,o))}})),r},l.for=(e,t,n)=>{const o=document.createTextNode(""),r=l.html("div",{style:"display:contents"},[o]),s=new Map;return l.watch((()=>{const a=("function"==typeof e?e():e)||[],c=new Set;a.forEach(((e,a)=>{const l=n(e,a);c.add(l);let d=s.get(l);d||(d=i((()=>t(e,a))),s.set(l,d)),r.insertBefore(d.container,o)})),s.forEach(((e,t)=>{c.has(t)||(e.destroy(),s.delete(t))}))})),r},l.router=e=>{const t=l(window.location.hash.replace(/^#/,"")||"/");window.addEventListener("hashchange",(()=>t(window.location.hash.replace(/^#/,"")||"/")));const n=Div({class:"router-outlet"});let o=null;return l.watch([t],(()=>{o&&o.destroy();const r=t(),s=e.find((e=>{const t=e.path.split("/").filter(Boolean),n=r.split("/").filter(Boolean);return t.length===n.length&&t.every(((e,t)=>e.startsWith(":")||e===n[t]))}))||e.find((e=>"*"===e.path));if(s){const e={};s.path.split("/").filter(Boolean).forEach(((t,n)=>{t.startsWith(":")&&(e[t.slice(1)]=r.split("/").filter(Boolean)[n])})),o=i((()=>{const t=s.component(e);return"function"==typeof t?t():t})),n.appendChild(o.container)}})),n},l.go=e=>window.location.hash=e.replace(/^#?\/?/,"#/"),l.mount=(e,t)=>{const n="string"==typeof t?document.querySelector(t):t;if(!n)return;r.has(n)&&r.get(n).destroy();const o=i("function"==typeof e?e:()=>e);return n.replaceChildren(o.container),r.set(n,o),o};"div span p h1 h2 h3 h4 h5 h6 br hr section article aside nav main header footer address ul ol li dl dt dd a em strong small i b u mark time sub sup pre code blockquote details summary dialog form label input textarea select button option fieldset legend table thead tbody tfoot tr th td caption img video audio canvas svg iframe picture source progress meter".split(/\s+/).forEach((e=>{window[e.charAt(0).toUpperCase()+e.slice(1)]=(t,n)=>l.html(e,t,n)})),window.$=l})();export const{$:$}=window; \ No newline at end of file diff --git a/src/docs/.vitepress/config.js b/src/docs/.vitepress/config.js index 81fe6b5..b1937cc 100644 --- a/src/docs/.vitepress/config.js +++ b/src/docs/.vitepress/config.js @@ -41,9 +41,7 @@ export default defineConfig({ items: [ { text: 'Quick Start', link: '/api/quick' }, { text: '$', link: '/api/signal' }, - { text: '$.effect', link: '/api/effect' }, - { text: '$.ignore', link: '/api/ignore' }, - { text: '$.view', link: '/api/view' }, + { text: '$.watch', link: '/api/watch' }, { text: '$.html', link: '/api/html' }, { text: '$.router', link: '/api/router' }, { text: '$.mount', link: '/api/mount' }, diff --git a/src/docs/api/effect.md b/src/docs/api/effect.md deleted file mode 100644 index a2b572b..0000000 --- a/src/docs/api/effect.md +++ /dev/null @@ -1,90 +0,0 @@ -# ⚡ Side Effects: `$.effect( )` - -The `$.effect` function allows you to run a piece of code whenever the signals it depends on are updated. It automatically tracks any signal called within its body. - -## 🛠 Function Signature - -```typescript -$.effect(callback: Function): StopFunction -``` - -| Parameter | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **`callback`** | `Function` | Yes | The code to run. It will execute immediately and then re-run on dependency changes. | - -**Returns:** A `StopFunction` that, when called, cancels the effect and prevents further executions. - ---- - -## 📖 Usage Patterns - -### 1. Basic Tracking -Any signal you "touch" inside the effect becomes a dependency. - -```javascript -const count = $(0); - -$.effect(() => { - // This runs every time 'count' changes - console.log(`The count is now: ${count()}`); -}); - -count(1); // Console: "The count is now: 1" -``` - -### 2. Manual Cleanup -If your effect creates something that needs to be destroyed (like a timer or a global event listener), you can return a cleanup function. - -```javascript -$.effect(() => { - const timer = setInterval(() => console.log("Tick"), 1000); - - // SigPro will run this BEFORE the next effect execution - // or when the effect is stopped. - return () => clearInterval(timer); -}); -``` - -### 3. Nesting & Automatic Cleanup -If you create a signal or another effect inside an effect, SigPro tracks them as "children". When the parent effect re-runs or stops, all children are automatically cleaned up to prevent memory leaks. - -```javascript -$.effect(() => { - if (isLoggedIn()) { - // This sub-effect is only active while 'isLoggedIn' is true - $.effect(() => { - console.log("Fetching user data..."); - }); - } -}); -``` - ---- - -## 🛑 Stopping an Effect -You can stop an effect manually by calling the function it returns. This is useful for one-time operations or complex logic. - -```javascript -const stop = $.effect(() => { - console.log(count()); -}); - -// Later... -stop(); // The effect is destroyed and will never run again. -``` - ---- - -## 💡 Pro Tip: Batching -SigPro uses a **Microtask Queue** to handle updates. If you update multiple signals at once, the effect will only run **once** at the end of the current task. - -```javascript -const a = $(0); -const b = $(0); - -$.effect(() => console.log(a(), b())); - -// This triggers only ONE re-run, not two. -a(1); -b(2); -``` diff --git a/src/docs/api/html.md b/src/docs/api/html.md index 84e2582..fa0c110 100644 --- a/src/docs/api/html.md +++ b/src/docs/api/html.md @@ -1,6 +1,6 @@ # 🏗️ The DOM Factory: `$.html( )` -`$.html` is the internal engine that creates, attributes, and attaches reactivity to DOM elements. It is the foundation for all Tag Constructors in SigPro. +`$.html` is the internal engine that creates, attributes, and attaches reactivity to DOM elements. In V2, it uses `$.watch` to maintain a live, high-performance link between your Signals and the Document Object Model. ## 🛠 Function Signature @@ -41,7 +41,7 @@ $.html("button", { ``` ### 3. Reactive Attributes -If an attribute value is a **function** (like a Signal), `$.html` creates an internal `$.effect` to keep the DOM in sync with the state. +If an attribute value is a **function** (like a Signal), `$.html` creates an internal **`$.watch`** to keep the DOM in sync with the state. ```javascript $.html("div", { @@ -51,7 +51,7 @@ $.html("div", { ``` ### 4. Reactive Children -Children can be static or dynamic. When a child is a function, SigPro creates a reactive boundary for that specific part of the DOM. +Children can be static or dynamic. When a child is a function, SigPro creates a reactive boundary using `$.watch` for that specific part of the DOM. ```javascript $.html("div", {}, [ @@ -74,9 +74,10 @@ $.html("input", { }); ``` -## 🧹 Automatic Cleanup -Every element created with `$.html` gets a hidden `._cleanups` property (a `Set`). -* When SigPro removes an element via `$.view` or `$.router`, it automatically executes all functions stored in this Set (stopping effects, removing listeners, etc.). +## 🧹 Memory Management (Internal) +Every element created with `$.html` is "self-aware" regarding its reactive dependencies. +* **`._cleanups`**: A hidden `Set` attached to the element that stores all `stop()` functions from its internal `$.watch` calls and event listeners. +* **Lifecycle**: When an element is removed by a Controller (`$.If`, `$.For`, or `$.router`), SigPro performs a recursive "sweep" to execute these cleanups, ensuring **zero memory leaks**. --- diff --git a/src/docs/api/ignore.md b/src/docs/api/ignore.md deleted file mode 100644 index b25503e..0000000 --- a/src/docs/api/ignore.md +++ /dev/null @@ -1,75 +0,0 @@ -# 🛑 Untracking: `$.ignore( )` - -The `$.ignore` function allows you to read a signal's value inside an effect or a computed signal **without** creating a dependency. - -## 🛠 Function Signature - -```typescript -$.ignore(callback: Function): any -``` - -| Parameter | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **`callback`** | `Function` | Yes | A function where signals can be read "silently". | - -**Returns:** Whatever the callback function returns. - ---- - -## 📖 Usage Patterns - -### 1. Preventing Dependency Tracking -Normally, reading a signal inside `$.effect` makes the effect re-run when that signal changes. `$.ignore` breaks this link. - -```javascript -const count = $(0); -const logLabel = $("System Log"); - -$.effect(() => { - // This effect tracks 'count'... - const currentCount = count(); - - // ...but NOT 'logLabel'. - // Changing 'logLabel' will NOT re-run this effect. - const label = $.ignore(() => logLabel()); - - console.log(`${label}: ${currentCount}`); -}); - -count(1); // Console: "System Log: 1" (Triggers re-run) -logLabel("UI"); // Nothing happens in console (Ignored) -``` - -### 2. Reading State in Event Handlers -Inside complex UI logic, you might want to take a "snapshot" of a signal without triggering a reactive chain. - -```javascript -const handleClick = () => { - // Accessing state without letting the caller know we touched it - const data = $.ignore(() => mySignal()); - process(data); -}; -``` - -### 3. Avoiding Infinite Loops -If you need to **write** to a signal based on its own value inside an effect (and you aren't using the functional updater), `$.ignore` prevents the effect from triggering itself. - -```javascript -$.effect(() => { - const value = someSignal(); - - if (value > 100) { - // We update the signal, but we ignore the read to avoid a loop - $.ignore(() => someSignal(0)); - } -}); -``` - ---- - -## 💡 Why use it? - -* **Performance:** Prevents expensive effects from running when non-essential data changes. -* **Logic Control:** Allows "sampling" a signal at a specific point in time. -* **Safety:** Essential for complex state orchestrations where circular dependencies might occur. - diff --git a/src/docs/api/mount.md b/src/docs/api/mount.md index ec43540..f6b47d1 100644 --- a/src/docs/api/mount.md +++ b/src/docs/api/mount.md @@ -1,8 +1,8 @@ # 🔌 Application Mounter: `$.mount( )` -The `$.mount` function is the entry point of your reactive world. It bridges the gap between your SigPro logic and the browser's Real DOM by injecting a component into the document. +The `$.mount` function is the entry point of your reactive world. It bridges the gap between your SigPro logic and the browser's Real DOM by injecting a component into the document and initializing its reactive lifecycle. -## 1. Function Signature +## 🛠️ Function Signature ```typescript $.mount(node: Function | HTMLElement, target?: string | HTMLElement): RuntimeObject @@ -13,100 +13,73 @@ $.mount(node: Function | HTMLElement, target?: string | HTMLElement): RuntimeObj | **`node`** | `Function` or `Node` | **Required** | The component function or DOM element to render. | | **`target`** | `string` or `Node` | `document.body` | CSS selector or DOM element where the app will live. | -**Returns:** A `Runtime` object containing the `container` and a `destroy()` method. +**Returns:** A `Runtime` object containing the `container` and a `destroy()` method to wipe all reactivity and DOM nodes. --- -## 2. Common Usage Scenarios +## 📖 Common Usage Scenarios -### A. The "Clean Slate" (SPA Entry) -In a modern Single Page Application, you typically want SigPro to manage the entire view. By default, if no target is provided, it mounts to `document.body`. +### 1. The SPA Entry Point +In a Single Page Application, you typically mount your main component to the body or a root div. SigPro manages the entire view from that point. ```javascript import { $ } from './sigpro.js'; import App from './App.js'; -// Mounts your main App component directly to the body -$.mount(App); +// Mounts your main App component +$.mount(App, '#app-root'); ``` -### B. Targeting a Specific Container -If your HTML has a predefined structure, you can tell SigPro exactly where to render by passing a CSS selector or a direct reference. - -```html - -
-``` +### 2. Reactive "Islands" +SigPro is perfect for adding reactivity to static pages. You can mount small widgets into specific parts of an existing HTML layout. ```javascript -// Mount using a CSS selector -$.mount(MyComponent, '#app-root'); - -// Mount using a direct DOM reference -const sidebar = document.querySelector('#sidebar'); -$.mount(SidebarComponent, sidebar); -``` - -### C. Creating "Reactive Islands" -SigPro is excellent for "sprinkling" reactivity onto legacy or static pages. You can inject small reactive widgets into any part of an existing HTML layout. - -```javascript -// A small reactive widget -const CounterWidget = () => { +const Counter = () => { const count = $(0); return Button({ onclick: () => count(c => c + 1) }, [ "Clicks: ", count ]); }; -// Mount it into a specific div in your static HTML -$.mount(CounterWidget, '#counter-container'); +// Mount only the counter into a specific sidebar div +$.mount(Counter, '#sidebar-widget'); ``` --- -## 3. How it Works (Lifecycle) +## 🔄 How it Works (Lifecycle & Saneamiento) -When `$.mount` is executed, it performs these critical steps: +When `$.mount` is executed, it performs these critical steps to ensure a leak-free environment: -1. **Resolution & Wrapping**: If you pass a **Function**, SigPro wraps it in a `$.view()`. This starts tracking all internal signals and effects. -2. **Target Clearance**: It uses `target.replaceChildren()`. This efficiently wipes any existing HTML or "zombie" nodes inside the target before mounting. -3. **Injection**: The component's container is appended to the target. -4. **Memory Management**: It stores the `Runtime` instance associated with that DOM element. If you call `$.mount` again on the same target, SigPro automatically **destroys the previous app** to prevent memory leaks. +1. **Duplicate Detection**: If you call `$.mount` on a target that already has a SigPro instance, it automatically calls `.destroy()` on the previous instance. This prevents "Zombie Effects" from stacking in memory. +2. **Internal Scoping**: It executes the component function inside an internal **Reactive Owner**. This captures every `$.watch` and event listener created during the render. +3. **Target Injection**: It clears the target using `replaceChildren()` and appends the new component. +4. **Runtime Creation**: It returns a control object: + * `container`: The actual DOM element created. + * `destroy()`: The "kill switch" that runs all cleanups, stops all watchers, and removes the element from the DOM. --- -## 4. Global vs. Local Scope +## 🛑 Manual Unmounting -### The "Framework" Way (Global) -By importing your core in your entry file, SigPro automatically initializes global Tag Constructors (`Div`, `Span`, `H1`, etc.). This allows for a clean, declarative DX across your entire project. +While SigPro handles most cleanups automatically (via `$.If`, `$.For`, and `$.router`), you can manually destroy any mounted instance. This is vital for imperatively managed UI like **Toasts** or **Modals**. ```javascript -// main.js -import './sigpro.js'; +const instance = $.mount(MyToast, '#toast-container'); -// Now any file can simply do: -$.mount(() => H1("Global SigPro App")); -``` - -### The "Library" Way (Local) -If you prefer to avoid global variables, you can use the low-level `$.html` factory to create elements locally. - -```javascript -import { $ } from './sigpro.js'; - -const myNode = $.html('div', { class: 'widget' }, 'Local Instance'); -$.mount(myNode, '#widget-target'); +// Later, to remove the toast and kill its reactivity: +instance.destroy(); ``` --- -## 5. Summary Cheat Sheet +## 💡 Summary Cheat Sheet | Goal | Code Pattern | | :--- | :--- | | **Mount to body** | `$.mount(App)` | -| **Mount to ID** | `$.mount(App, '#root')` | -| **Mount to Element** | `$.mount(App, myElement)` | -| **Mount raw Node** | `$.mount(Div("Hello"), '#id')` | -| **Unmount/Destroy** | `const app = $.mount(App); app.destroy();` | +| **Mount to CSS Selector** | `$.mount(App, '#root')` | +| **Mount to DOM Node** | `$.mount(App, myElement)` | +| **Clean & Re-mount** | Calling `$.mount` again on the same target | +| **Total Saneamiento** | `const app = $.mount(App); app.destroy();` | + diff --git a/src/docs/api/tags.md b/src/docs/api/tags.md index b10772c..aad20d3 100644 --- a/src/docs/api/tags.md +++ b/src/docs/api/tags.md @@ -1,6 +1,6 @@ # 🎨 Global Tag Helpers -In **SigPro**, you don't need to manually type `$.html('div', ...)` for every element. To keep your code declarative and readable, the engine automatically generates **Global Helper Functions** for all standard HTML5 tags upon initialization. +In **SigPro V2**, you don't need to manually type `$.html('div', ...)` for every element. To keep your code declarative and readable, the engine automatically generates **Global Helper Functions** for all standard HTML5 tags upon initialization. ## 1. How it Works @@ -59,23 +59,23 @@ Span(42); --- -## 4. Reactive Power Examples +## 4. Reactive Power V2 -These helpers are natively wired into SigPro's reactivity. No manual `useEffect` or `watch` calls are needed. +These helpers are natively wired into SigPro's **`$.watch`** engine. No manual effect management is needed; the lifecycle is tied to the DOM node. ### Reactive Attributes -Simply pass a Signal (function) to any attribute. SigPro handles the rest. +Simply pass a Signal (function) to any attribute. SigPro creates an internal `$.watch` to keep the DOM in sync. ```javascript const theme = $("light"); Div({ - // Updates 'class' automatically when theme() changes + // Updates 'class' automatically via internal $.watch class: () => `app-box ${theme()}` }, "Themeable Box"); ``` ### The Binding Operator (`$`) -Use the `$` prefix for **Two-Way Binding** on inputs. +Use the `$` prefix for **Two-Way Binding** on inputs. This bridges the Signal and the Input element bi-directionally. ```javascript const search = $(""); @@ -86,13 +86,13 @@ Input({ }); ``` -### Dynamic Lists -Combine tags with `ui.For` for high-performance list rendering. +### Dynamic Flow & Saneamiento +Combine tags with Core controllers for high-performance rendering. SigPro automatically cleans up the `$.watch` instances when nodes are removed. ```javascript const items = $(["Apple", "Banana", "Cherry"]); Ul({ class: "list-disc" }, [ - ui.For(items, (item) => Li(item), (item) => item) + $.For(items, (item) => Li(item)) ]); ``` @@ -103,13 +103,13 @@ Ul({ class: "list-disc" }, [ Since SigPro injects these helpers into the global `window` object, follow these rules to avoid bugs: 1. **Avoid Shadowing**: Don't name your local variables like the tags (e.g., `const Div = ...`). This will "hide" the SigPro helper. -2. **Custom Components**: Always use **PascalCase**, **UPPERCASE**, or **Underscore** prefixes for your own component functions (e.g., `UserCard`, `INPUT`, or `_Input`) to distinguish them from the built-in Tag Helpers and avoid naming collisions. +2. **Custom Components**: Always use **PascalCase** for your own component functions (e.g., `UserCard`, `NavMenu`) to distinguish them from the built-in Tag Helpers and maintain architectural clarity. ::: --- -## 6. Logic to UI Comparison +## 5. Logic to UI Comparison -Here is how a dynamic **User Status** component translates from SigPro logic to the final DOM structure. +Here is how a dynamic **User Status** component translates from SigPro logic to the final DOM structure, handled by the V2 "Saneamiento" engine. ```javascript // SigPro Component @@ -121,16 +121,14 @@ const UserStatus = (name, $online) => ( class: 'w-3 h-3 bg-green-500 rounded-full' }), P({ - // Reactive text content + // Reactive text content via automatic $.watch class: () => $online() ? "text-bold" : "text-gray-400" }, name) ]) ); ``` -| State (`$online`) | Rendered HTML | -| :--- | :--- | -| **`true`** | `

John

` | -| **`false`** | `

John

` | - - +| State (`$online`) | Rendered HTML | Memory Management | +| :--- | :--- | :--- | +| **`true`** | `

John

` | Watcher active | +| **`false`** | `

John

` | Attribute synced | diff --git a/src/docs/api/view.md b/src/docs/api/view.md deleted file mode 100644 index b2b5299..0000000 --- a/src/docs/api/view.md +++ /dev/null @@ -1,78 +0,0 @@ -# 🖼️ Component Lifecycle: `$.view( )` - -The `$.view` function is a specialized wrapper used to manage the lifecycle of a UI component. It tracks all signals, effects, and DOM elements created within it, providing a single point of destruction to prevent memory leaks. - -## 🛠 Function Signature - -```typescript -$.view(renderFn: Function): RuntimeObject -``` - -| Parameter | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **`renderFn`** | `Function` | Yes | A function that returns a DOM Node or an array of Nodes. | - -**Returns:** A `Runtime` object containing: -* `container`: A `div` (with `display: contents`) holding the rendered content. -* `destroy()`: A function that unmounts the view and cleans up all internal effects/listeners. - ---- - -## 📖 Usage Patterns - -### 1. Basic Component Wrapper -When you wrap logic in `$.view`, SigPro creates a "boundary". - -```javascript -const myView = $.view(() => { - const count = $(0); - - // This effect is "owned" by this view - $.effect(() => console.log(count())); - - return Div([ - H1("Internal View"), - Button({ onclick: () => count(c => c + 1) }, "Add") - ]); -}); - -// To show it: -document.body.appendChild(myView.container); - -// To kill it (removes from DOM and stops all internal effects): -myView.destroy(); -``` - -### 2. Manual Cleanups with `onCleanup` -The `renderFn` receives a helper object that allows you to register custom cleanup logic (like closing a WebSocket or a third-party library instance). - -```javascript -const ChartComponent = () => $.view(({ onCleanup }) => { - const socket = new WebSocket("ws://..."); - - onCleanup(() => { - socket.close(); - console.log("Cleanup: WebSocket closed"); - }); - - return Div({ class: "chart-container" }); -}); -``` - ---- - -## 🏗️ Internal Architecture - -When `$.view` is called, it performs the following: -1. **Isolation**: It creates a new `Set` of cleanups. -2. **Execution**: It runs your function and captures any `$.effect` or child `$.view` created during execution. -3. **Container**: It wraps the result in a `display: contents` element (which doesn't affect your CSS layout). -4. **Disposal**: When `destroy()` is called, it recursively calls all cleanup functions and removes the container from the DOM. - ---- - -## 💡 Why use `$.view`? - -* **Automatic Cleanup**: You don't have to manually stop every effect when a user navigates away. -* **Router Integration**: The SigPro Router (`$.router`) uses `$.view` internally to swap pages and clean up the previous one automatically. -* **Performance**: It ensures that background processes (like intervals or observers) stop as soon as the element is no longer visible. diff --git a/src/docs/api/watch.md b/src/docs/api/watch.md new file mode 100644 index 0000000..62b4a8e --- /dev/null +++ b/src/docs/api/watch.md @@ -0,0 +1,91 @@ +# ⚡ Reactivity Control: `$.watch( )` + +The `$.watch` function is the reactive engine of SigPro. It allows you to execute code automatically when signals change. In V2, `$.watch` is **polymorphic**: it can track dependencies automatically or follow an explicit list. + +## 🛠 Function Signature + +```typescript +// Automatic Mode (Magic Tracking) +$.watch(callback: Function): StopFunction + +// Explicit Mode (Isolated Dependencies) +$.watch(deps: Signal[], callback: Function): StopFunction +``` + +| Parameter | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **`target / deps`** | `Function` | `Array` | Yes | Either the code to run (Auto) or an array of signals to watch (Explicit). | +| **`callback`** | `Function` | Only in Explicit | The code that will run when the `deps` change. | + +**Returns:** A `StopFunction` that, when called, destroys the watcher and releases memory. + +--- + +## 📖 Usage Patterns + +### 1. Automatic Mode (Default) +Any signal you "touch" inside the callback becomes a dependency. SigPro tracks them behind the scenes. + +```javascript +const count = $(0); + +$.watch(() => { + // Re-runs every time 'count' changes + console.log(`Count is: ${count()}`); +}); +``` + +### 2. Explicit Mode (Advanced Saneamiento) 🚀 +This mode **isolates** execution. The callback only triggers when the signals in the array change. Any other signal accessed *inside* the callback will NOT trigger a re-run. This is the "gold standard" for Routers and heavy components. + +```javascript +const sPath = $("/home"); +const user = $("Admin"); + +$.watch([sPath], () => { + // Only triggers when 'sPath' changes. + // Changes to 'user' will NOT trigger this, preventing accidental re-renders. + console.log(`Navigating to ${sPath()} as ${user()}`); +}); +``` + +### 3. Automatic Cleanup +If your logic creates timers, event listeners, or other reactive effects, SigPro tracks them as "children" of the current watch. When the watcher re-runs or stops, it kills everything inside automatically. + +```javascript +$.watch(() => { + const timer = setInterval(() => console.log("Tick"), 1000); + + // Register a manual cleanup if needed + // Or simply rely on SigPro to kill nested $.watch() calls + return () => clearInterval(timer); +}); +``` + +--- + +## 🛑 Stopping a Watcher +Call the returned function to manually kill the watcher. This is essential for manual DOM injections (like Toasts) or long-lived background processes. + +```javascript +const stop = $.watch(() => console.log(count())); + +// Later... +stop(); // The link between the signal and this code is physically severed. +``` + +--- + +## 💡 Pro Tip: The Microtask Queue +SigPro batches updates. If you update multiple signals in the same execution block, the watcher will only fire **once** at the end of the task. + +```javascript +const a = $(0); +const b = $(0); + +$.watch(() => console.log(a(), b())); + +// This triggers only ONE re-run. +a(1); +b(2); +``` diff --git a/src/docs/ui/quick.md b/src/docs/ui/quick.md index 4a9933a..9339cfe 100644 --- a/src/docs/ui/quick.md +++ b/src/docs/ui/quick.md @@ -1,15 +1,25 @@ + # 🧩 UI Components `(WIP)` -> **Status: Work In Progress.** > These are high-level, complex visual components designed to speed up development. They often replace native HTML elements with "superpowered" versions that handle their own internal logic, reactivity, and accessibility. +> **Status: Work In Progress.** > These are high-level, complex visual components designed to speed up development. They replace native HTML elements with "superpowered" versions that handle their own internal logic, reactivity, and professional styling. + +## ⚠️ Prerequisites + +To ensure all components render correctly with their reactive themes and states, your project **must** have the following versions installed: + +* **Tailwind CSS v4+**: For the new engine performance and modern CSS variables. +* **DaisyUI v5+**: Required for the updated theme-selectors and improved component classes used in the SigPro UI library. + +--- ## 1. What are UI Components? -Unlike **Tag Helpers** (which are just functional mirrors of HTML tags), SigPro UI Components are smart abstractions. +Unlike **Tag Helpers** (which are just functional mirrors of HTML tags), SigPro UI Components are smart abstractions: * **Stateful**: They manage complex internal states (like date ranges, search filtering, or API lifecycles). -* **Reactive**: Attributes prefixed with `$` are automatically tracked. -* **Self-Cleaning**: They automatically use `_cleanups` to destroy observers or event listeners when removed from the DOM. -* **Themed**: Fully compatible with Tailwind CSS and DaisyUI themes. +* **Reactive**: Attributes prefixed with `$` are automatically tracked via `$.watch`. +* **Self-Sane**: They automatically use `._cleanups` to destroy observers or event listeners when removed from the DOM. +* **Themed**: Fully compatible with the DaisyUI v5 theme system and Tailwind v4 utility classes. --- @@ -17,7 +27,6 @@ Unlike **Tag Helpers** (which are just functional mirrors of HTML tags), SigPro | Category | Components | | :--- | :--- | -| **Logic & Flow** | `If`, `For`, `Json` | | **Forms & Inputs** | `Button`, `Input`, `Select`, `Autocomplete`, `Datepicker`, `Colorpicker`, `CheckBox`, `Radio`, `Range`, `Rating`, `Swap` | | **Feedback** | `Alert`, `Toast`, `Modal`, `Loading`, `Badge`, `Tooltip`, `Indicator` | | **Navigation** | `Navbar`, `Menu`, `Drawer`, `Tabs`, `Accordion`, `Dropdown` | @@ -28,7 +37,7 @@ Unlike **Tag Helpers** (which are just functional mirrors of HTML tags), SigPro ## 3. Examples with "Superpowers" ### A. The Declarative API Flow (`Request` & `Response`) -Instead of manually managing `loading` and `error` flags, use these two together to handle data fetching elegantly. +Instead of manually managing `loading` and `error` flags, use these together to handle data fetching elegantly. ```javascript // 1. Define the request (it tracks dependencies automatically) @@ -48,7 +57,7 @@ Div({ class: "p-4" }, [ ``` ### B. Smart Inputs & Autocomplete -Native inputs are boring. SigPro UI inputs handle labels, icons, password toggles, and validation states out of the box. +SigPro UI inputs handle labels, icons, password toggles, and validation states out of the box using DaisyUI v5 classes. ```javascript const searchQuery = $(""); @@ -63,7 +72,7 @@ Autocomplete({ ``` ### C. The Reactive Datepicker -Handles single dates or ranges with a clean, reactive interface. +Handles single dates or ranges with a clean, reactive interface that automatically syncs with your signals. ```javascript const myDate = $(""); // or { start: "", end: "" } for range @@ -71,15 +80,15 @@ const myDate = $(""); // or { start: "", end: "" } for range Datepicker({ label: "Select Expiry Date", $value: myDate, - range: false // Set to true for range selection + range: false }); ``` ### D. Imperative Toasts & Modals -Sometimes you just need to trigger a message without cluttering your template. +Trigger complex UI elements from your logic. These components use `$.mount` internally to ensure they are properly cleaned up from memory after they close. ```javascript -// Show a notification from anywhere in your logic +// Show a notification (Self-destroying after 3s) Toast("Settings saved successfully!", "alert-success", 3000); // Control a modal with a simple signal @@ -104,15 +113,15 @@ The UI library comes with a built-in locale system. It currently supports `es` a // Set the global UI language SetLocale("en"); -// Access translated strings in your own components -const t = tt("confirm"); // Returns a signal that tracks the current locale +// Access translated strings (Returns a signal that tracks the current locale) +const t = tt("confirm"); ``` --- ## 5. Best Practices -* **Use `$` for Reactivity**: If a property starts with `$`, it expects a Signal (e.g., `$value: mySignal`). -* **Key your Lists**: When using `For`, always provide a `keyFn` to ensure high-performance DOM reconciliation. -* **Cleanups**: If you build custom components that use `setInterval` or `observers`, add them to the element's `_cleanups` Set. +* **Use `$` for Reactivity**: If a property starts with `$`, the component expects a Signal (e.g., `$value: mySignal`). +* **Automatic Cleaning**: You don't need to manually destroy these components if they are inside a `$.If` or `$.For`. SigPro's core will "sweep" their internal watchers automatically. +* **Manual Cleanups**: If you build custom components using `setInterval` or third-party observers, always add the stop functions to the element's `._cleanups` Set.