From 99780e8399ec03aabee7c8d6d8c1811c7a91dd66 Mon Sep 17 00:00:00 2001 From: natxocc Date: Mon, 27 Apr 2026 15:22:57 +0200 Subject: [PATCH] New modular Sigpro --- dist/sigpro.esm.js | 129 ++------- dist/sigpro.esm.min.js | 2 +- dist/sigpro.js | 165 ++++-------- dist/sigpro.min.js | 2 +- docs/README.md | 17 -- docs/_sidebar.md | 4 +- docs/api/fx.md | 184 ------------- docs/api/global.md | 1 + docs/api/mount.md | 1 + docs/api/quick.md | 125 +++------ docs/api/req.md | 184 ------------- docs/api/router.md | 1 + docs/api/tags.md | 113 ++++---- docs/install.md | 71 ++--- docs/sigpro-full.js | 589 +++++++++++++++++++++++++++++++++++++++++ docs/sigpro.js | 132 ++------- package.json | 49 ++-- sigpro-full.js | 12 + sigpro.js | 137 ++-------- sigpro/tags.js | 2 +- sigpro/xss.js | 4 +- 21 files changed, 888 insertions(+), 1036 deletions(-) delete mode 100644 docs/api/fx.md delete mode 100644 docs/api/req.md create mode 100644 docs/sigpro-full.js create mode 100644 sigpro-full.js diff --git a/dist/sigpro.esm.js b/dist/sigpro.esm.js index d313c44..b9b6cad 100644 --- a/dist/sigpro.esm.js +++ b/dist/sigpro.esm.js @@ -15,6 +15,10 @@ var MOUNTED_NODES = new WeakMap; var SVG_NS = "http://www.w3.org/2000/svg"; var XLINK_NS = "http://www.w3.org/1999/xlink"; var SVG_TAGS = new Set("svg,path,circle,rect,line,polyline,polygon,g,defs,text,textPath,tspan,use,symbol,image,marker,ellipse".split(",")); +var attrFilter = null; +var filterXSS = (fn) => { + attrFilter = fn; +}; var dispose = (eff) => { if (!eff || eff._disposed) return; @@ -243,21 +247,6 @@ var cleanupNode = (node) => { if (node.childNodes) node.childNodes.forEach((n) => cleanupNode(n)); }; -var DANGEROUS_PROTOCOL = /^\s*(javascript|data|vbscript):/i; -var DANGEROUS_URI_ATTRS = new Set(["src", "href", "formaction", "action", "background", "code", "archive"]); -var isDangerousAttr = (key) => DANGEROUS_URI_ATTRS.has(key) || key.startsWith("on"); -var validateAttr = (key, val) => { - if (val == null || val === false) - return null; - if (isDangerousAttr(key)) { - const sVal = String(val); - if (DANGEROUS_PROTOCOL.test(sVal)) { - console.warn(`[SigPro] Bloqueado protocolo peligroso en ${key}`); - return "#"; - } - } - return val; -}; var h = (tag, props = {}, children = []) => { if (props instanceof Node || isArr(props) || !isObj(props)) { children = props; @@ -296,38 +285,38 @@ var h = (tag, props = {}, children = []) => { isFunc(v) ? v(el) : v.current = el; continue; } + let val = attrFilter ? attrFilter(k, v) : v; if (isSVG && k.startsWith("xlink:")) { - const cleanVal = validateAttr(k.slice(6), v); - cleanVal == null ? el.removeAttributeNS(XLINK_NS, k.slice(6)) : el.setAttributeNS(XLINK_NS, k.slice(6), cleanVal); + val == null ? el.removeAttributeNS(XLINK_NS, k.slice(6)) : el.setAttributeNS(XLINK_NS, k.slice(6), val); continue; } if (k.startsWith("on")) { const ev = k.slice(2).toLowerCase(); - el.addEventListener(ev, v); - const off = () => el.removeEventListener(ev, v); + el.addEventListener(ev, val); + const off = () => el.removeEventListener(ev, val); el._cleanups.add(off); onUnmount(off); - } else if (isFunc(v)) { + } else if (isFunc(val)) { const effect = createEffect(() => { - const val = validateAttr(k, v()); + const raw = val(); + const safeVal = attrFilter ? attrFilter(k, raw) : raw; if (k === "class") - el.className = val || ""; - else if (val == null) + el.className = safeVal || ""; + else if (safeVal == null) el.removeAttribute(k); else if (k in el && !isSVG) - el[k] = val; + el[k] = safeVal; else - el.setAttribute(k, val === true ? "" : val); + el.setAttribute(k, safeVal === true ? "" : safeVal); }); effect(); el._cleanups.add(() => dispose(effect)); onUnmount(() => dispose(effect)); if (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) && (k === "value" || k === "checked")) { const evType = k === "checked" ? "change" : "input"; - el.addEventListener(evType, (ev) => v(ev.target[k])); + el.addEventListener(evType, (ev) => val(ev.target[k])); } } else { - const val = validateAttr(k, v); if (val != null) { if (k in el && !isSVG) el[k] = val; @@ -433,35 +422,6 @@ var when = (cond, SIP, NOP = null) => { onUnmount(() => currentView?.destroy()); return root; }; -var fx = ({ name, duration = 200, scale, slide, rotate, blur }, child) => { - const el = typeof child === "function" ? child() : child; - if (!(el instanceof Node)) - return el; - if (name) { - el.style.animation = `${name}-in ${duration}ms`; - return el; - } - const hasTransform = scale || slide || rotate || blur; - const initialTransform = [ - scale ? "scale(0.95)" : "", - slide ? "translateY(-10px)" : "", - rotate ? "rotate(-2deg)" : "" - ].filter(Boolean).join(" "); - el.style.transition = `all ${duration}ms ease`; - el.style.opacity = "0"; - if (hasTransform) - el.style.transform = initialTransform; - if (blur) - el.style.filter = "blur(4px)"; - requestAnimationFrame(() => { - el.style.opacity = "1"; - if (hasTransform) - el.style.transform = "none"; - if (blur) - el.style.filter = "none"; - }); - return el; -}; var each = (src, itemFn, keyField) => { const anchor = doc.createTextNode(""); const root = h("div", { style: "display:contents" }, [anchor]); @@ -527,47 +487,6 @@ router.params = $({}); router.to = (p) => window.location.hash = p.replace(/^#?\/?/, "#/"); router.back = () => window.history.back(); router.path = () => window.location.hash.replace(/^#/, "") || "/"; -var req = ({ url, method = "GET", headers = {} }) => { - const loading = $(false); - const error = $(null); - const data = $(null); - let controller = null; - let timeoutId = null; - const run = async (body = null) => { - controller?.abort(); - clearTimeout(timeoutId); - controller = new AbortController; - timeoutId = setTimeout(() => controller.abort(), 1e4); - loading(true); - error(null); - try { - const isFormData = body instanceof FormData; - const res = await fetch(url, { - method, - headers: isFormData ? headers : { "Content-Type": "application/json", ...headers }, - body: isFormData ? body : body ? JSON.stringify(body) : undefined, - signal: controller.signal - }); - const text = await res.text(); - const json = text ? JSON.parse(text) : null; - if (!res.ok) - throw new Error(json?.message || res.statusText); - data(json); - return json; - } catch (e) { - if (e.name !== "AbortError") - error(e.message); - throw e; - } finally { - loading(false); - clearTimeout(timeoutId); - controller = null; - timeoutId = null; - } - }; - const abort = () => controller?.abort(); - return { run, abort, loading, error, data }; -}; var mount = (comp, target) => { const t = typeof target === "string" ? doc.querySelector(target) : target; if (!t) @@ -579,27 +498,13 @@ var mount = (comp, target) => { MOUNTED_NODES.set(t, inst); return inst; }; -var sigproFn = Object.freeze({ $, $$, watch, h, when, each, fx, router, req, mount, batch }); -var sigpro = () => { - if (typeof window !== "undefined") { - Object.assign(window, sigproFn); - "a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video".split(" ").forEach((tag) => { - window[tag] = (props, children) => h(tag, props, children); - }); - console.log("SigPro DX installed."); - } -}; -if (typeof import.meta === "undefined" && typeof window !== "undefined") - sigpro(); export { when, watch, - sigpro, router, - req, mount, h, - fx, + filterXSS, each, batch, $$, diff --git a/dist/sigpro.esm.min.js b/dist/sigpro.esm.min.js index 104715e..8f6235c 100644 --- a/dist/sigpro.esm.min.js +++ b/dist/sigpro.esm.min.js @@ -1 +1 @@ -var y=(e)=>typeof e==="function",D=(e)=>e&&typeof e==="object",b=Array.isArray,g=typeof document<"u"?document:null,P=(e)=>e?._isRuntime?e.container:e instanceof Node?e:g.createTextNode(e==null?"":String(e)),p=null,_=null,v=!1,O=0,R=new Set,I=new WeakMap,k=Symbol("iter"),$=new WeakMap,q="http://www.w3.org/2000/svg",U="http://www.w3.org/1999/xlink",F=new Set("svg,path,circle,rect,line,polyline,polygon,g,defs,text,textPath,tspan,use,symbol,image,marker,ellipse".split(",")),E=(e)=>{if(!e||e._disposed)return;e._disposed=!0;let o=[e];while(o.length){let n=o.pop();if(n._cleanups)n._cleanups.forEach((r)=>r()),n._cleanups.clear();if(n._children)n._children.forEach((r)=>o.push(r)),n._children.clear();if(n._deps)n._deps.forEach((r)=>r.delete(n)),n._deps.clear()}},x=(e)=>{if(_)(_._cleanups||=new Set).add(e)},W=(e)=>{let o=p;p=null;try{return e()}finally{p=o}},T=(e,o=!1)=>{let n=()=>{if(n._disposed)return;if(n._deps)n._deps.forEach((c)=>c.delete(n));if(n._cleanups)n._cleanups.forEach((c)=>c()),n._cleanups.clear();let r=p,s=_;p=_=n;try{return n._result=e()}catch(c){console.error("[SigPro]",c)}finally{p=r,_=s}};if(n._deps=n._cleanups=n._children=null,n._disposed=!1,n._isComputed=o,n._depth=p?p._depth+1:0,n._mounts=[],n._parent=_,_)(_._children||=new Set).add(n);return n},V=()=>{if(v)return;v=!0;let e=Array.from(R).sort((o,n)=>o._depth-n._depth);R.clear();for(let o of e)if(!o._disposed)o();v=!1},J=(e)=>{O++;try{return e()}finally{if(O--,O===0&&R.size>0&&!v)V()}},w=(e,o=!1)=>{if(!o&&p&&!p._disposed)e.add(p),(p._deps||=new Set).add(e);else if(o&&e.size>0){let n=!1;for(let r of e){if(r===p||r._disposed)continue;if(r._isComputed){if(r._dirty=!0,r._subs)w(r._subs,!0)}else R.add(r),n=!0}if(n&&!v&&O===0)queueMicrotask(V)}},S=(e,o=null)=>{let n=new Set;if(y(e)){let r,s=()=>{if(s._dirty){let c=p;p=s;try{let t=e();if(!Object.is(r,t))r=t,w(n,!0)}finally{p=c}s._dirty=!1}return w(n),r};return s._isComputed=!0,s._subs=n,s._dirty=!0,s._deps=null,s._disposed=!1,s}if(o)try{e=JSON.parse(localStorage.getItem(o))??e}catch(r){}return(...r)=>{if(r.length){let s=y(r[0])?r[0](e):r[0];if(!Object.is(e,s)){if(e=s,o)localStorage.setItem(o,JSON.stringify(e));w(n,!0)}}return w(n),e}},G=(e)=>{if(!D(e))return e;let o=I.get(e);if(o)return o;let n=new Map,r=(c)=>{let t=n.get(c);if(!t)n.set(c,t=new Set);return t},s=new Proxy(e,{get(c,t,i){if(typeof t!=="symbol")w(r(t));return G(Reflect.get(c,t,i))},set(c,t,i,l){let u=Reflect.has(c,t),a=Reflect.get(c,t,l),f=Reflect.set(c,t,i,l);if(f&&!Object.is(a,i)){if(w(r(t),!0),!u)w(r(k),!0)}return f},deleteProperty(c,t){let i=Reflect.deleteProperty(c,t);if(i)w(r(t),!0),w(r(k),!0);return i},ownKeys(c){return w(r(k)),Reflect.ownKeys(c)}});return I.set(e,s),s},C=(e,o)=>{if(o===void 0){let r=T(e);return r(),()=>E(r)}let n=T(()=>{let r=Array.isArray(e)?e.map((s)=>s()):e();W(()=>o(r))});return n(),()=>E(n)},L=(e)=>{if(!e)return;if(e._cleanups)e._cleanups.forEach((o)=>o()),e._cleanups.clear();if(e._ownerEffect)E(e._ownerEffect);if(e.childNodes)e.childNodes.forEach((o)=>L(o))},K=/^\s*(javascript|data|vbscript):/i,z=new Set(["src","href","formaction","action","background","code","archive"]),X=(e)=>z.has(e)||e.startsWith("on"),B=(e,o)=>{if(o==null||o===!1)return null;if(X(e)){let n=String(o);if(K.test(n))return console.warn(`[SigPro] Bloqueado protocolo peligroso en ${e}`),"#"}return o},A=(e,o={},n=[])=>{if(o instanceof Node||b(o)||!D(o))n=o,o={};if(y(e)){let t=T(()=>{let a=e(o,{children:n,emit:(f,...h)=>o[`on${f[0].toUpperCase()}${f.slice(1)}`]?.(...h)});return t._result=a,a});t();let i=t._result;if(i==null)return null;let l=i instanceof Node||b(i)&&i.every((a)=>a instanceof Node)?i:g.createTextNode(String(i)),u=(a)=>{if(D(a)&&!a._isRuntime)a._mounts=t._mounts||[],a._cleanups=t._cleanups||new Set,a._ownerEffect=t};return b(l)?l.forEach(u):u(l),l}let r=F.has(e),s=r?g.createElementNS(q,e):g.createElement(e);s._cleanups=new Set;for(let t of Object.keys(o)){let i=o[t];if(t==="ref"){y(i)?i(s):i.current=s;continue}if(r&&t.startsWith("xlink:")){let l=B(t.slice(6),i);l==null?s.removeAttributeNS(U,t.slice(6)):s.setAttributeNS(U,t.slice(6),l);continue}if(t.startsWith("on")){let l=t.slice(2).toLowerCase();s.addEventListener(l,i);let u=()=>s.removeEventListener(l,i);s._cleanups.add(u),x(u)}else if(y(i)){let l=T(()=>{let u=B(t,i());if(t==="class")s.className=u||"";else if(u==null)s.removeAttribute(t);else if(t in s&&!r)s[t]=u;else s.setAttribute(t,u===!0?"":u)});if(l(),s._cleanups.add(()=>E(l)),x(()=>E(l)),/^(INPUT|TEXTAREA|SELECT)$/.test(s.tagName)&&(t==="value"||t==="checked")){let u=t==="checked"?"change":"input";s.addEventListener(u,(a)=>i(a.target[t]))}}else{let l=B(t,i);if(l!=null)if(t in s&&!r)s[t]=l;else s.setAttribute(t,l===!0?"":l)}}let c=(t)=>{if(b(t))return t.forEach(c);if(y(t)){let i=g.createTextNode("");s.appendChild(i);let l=[],u=T(()=>{let a=t(),f=(b(a)?a:[a]).map(P);l.forEach((d)=>{if(d._isRuntime)d.destroy();else L(d);if(d.parentNode)d.remove()});let h=i;for(let d=f.length-1;d>=0;d--){let m=f[d];if(m.parentNode!==h.parentNode)h.parentNode?.insertBefore(m,h);if(m._mounts)m._mounts.forEach((M)=>M());h=m}l=f});u(),s._cleanups.add(()=>E(u)),x(()=>E(u))}else{let i=P(t);if(s.appendChild(i),i._mounts)i._mounts.forEach((l)=>l())}};return c(n),s},j=(e)=>{let o=new Set,n=_,r=p,s=g.createElement("div");s.style.display="contents",s.setAttribute("role","presentation"),_={_cleanups:o},p=null;let c=(t)=>{if(!t)return;if(t._isRuntime)o.add(t.destroy),s.appendChild(t.container);else if(b(t))t.forEach(c);else s.appendChild(t instanceof Node?t:g.createTextNode(String(t==null?"":t)))};try{c(e({onCleanup:(t)=>o.add(t)}))}finally{_=n,p=r}return{_isRuntime:!0,container:s,destroy:()=>{o.forEach((t)=>t()),L(s),s.remove()}}},Q=(e,o,n=null)=>{let r=g.createTextNode(""),s=A("div",{style:"display:contents"},[r]),c=null;return C(()=>!!(y(e)?e():e),(t)=>{if(c)c.destroy(),c=null;let i=t?o:n;if(i)c=j(()=>y(i)?i():i),s.insertBefore(c.container,r)}),x(()=>c?.destroy()),s},H=({name:e,duration:o=200,scale:n,slide:r,rotate:s,blur:c},t)=>{let i=typeof t==="function"?t():t;if(!(i instanceof Node))return i;if(e)return i.style.animation=`${e}-in ${o}ms`,i;let l=n||r||s||c,u=[n?"scale(0.95)":"",r?"translateY(-10px)":"",s?"rotate(-2deg)":""].filter(Boolean).join(" ");if(i.style.transition=`all ${o}ms ease`,i.style.opacity="0",l)i.style.transform=u;if(c)i.style.filter="blur(4px)";return requestAnimationFrame(()=>{if(i.style.opacity="1",l)i.style.transform="none";if(c)i.style.filter="none"}),i},Y=(e,o,n)=>{let r=g.createTextNode(""),s=A("div",{style:"display:contents"},[r]),c=new Map;return C(()=>(y(e)?e():e)||[],(t)=>{let i=new Map,l=[],u=t||[];for(let f=0;fo(h,f));else c.delete(d);i.set(d,m),l.push(m)}c.forEach((f)=>f.destroy());let a=r;for(let f=l.length-1;f>=0;f--){let d=l[f].container;if(d.nextSibling!==a)s.insertBefore(d,a);a=d}c=i}),s},N=(e)=>{let o=()=>window.location.hash.slice(1)||"/",n=S(o()),r=()=>n(o());window.addEventListener("hashchange",r),x(()=>window.removeEventListener("hashchange",r));let s=A("div",{class:"router-hook"}),c=null;return C([n],()=>{let t=n(),i=e.find((l)=>{let u=l.path.split("/").filter(Boolean),a=t.split("/").filter(Boolean);return u.length===a.length&&u.every((f,h)=>f[0]===":"||f===a[h])})||e.find((l)=>l.path==="*");if(i){c?.destroy();let l={};i.path.split("/").filter(Boolean).forEach((u,a)=>{if(u[0]===":")l[u.slice(1)]=t.split("/").filter(Boolean)[a]}),N.params(l),c=j(()=>y(i.component)?i.component(l):i.component),s.replaceChildren(c.container)}}),s};N.params=S({});N.to=(e)=>window.location.hash=e.replace(/^#?\/?/,"#/");N.back=()=>window.history.back();N.path=()=>window.location.hash.replace(/^#/,"")||"/";var Z=({url:e,method:o="GET",headers:n={}})=>{let r=S(!1),s=S(null),c=S(null),t=null,i=null;return{run:async(a=null)=>{t?.abort(),clearTimeout(i),t=new AbortController,i=setTimeout(()=>t.abort(),1e4),r(!0),s(null);try{let f=a instanceof FormData,h=await fetch(e,{method:o,headers:f?n:{"Content-Type":"application/json",...n},body:f?a:a?JSON.stringify(a):void 0,signal:t.signal}),d=await h.text(),m=d?JSON.parse(d):null;if(!h.ok)throw Error(m?.message||h.statusText);return c(m),m}catch(f){if(f.name!=="AbortError")s(f.message);throw f}finally{r(!1),clearTimeout(i),t=null,i=null}},abort:()=>t?.abort(),loading:r,error:s,data:c}},ee=(e,o)=>{let n=typeof o==="string"?g.querySelector(o):o;if(!n)return;if($.has(n))$.get(n).destroy();let r=j(y(e)?e:()=>e);return n.replaceChildren(r.container),$.set(n,r),r},te=Object.freeze({$:S,$$:G,watch:C,h:A,when:Q,each:Y,fx:H,router:N,req:Z,mount:ee,batch:J}),ne=()=>{if(typeof window<"u")Object.assign(window,te),"a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video".split(" ").forEach((e)=>{window[e]=(o,n)=>A(e,o,n)}),console.log("SigPro DX installed.")};if(typeof import.meta>"u"&&typeof window<"u")ne();export{Q as when,C as watch,ne as sigpro,N as router,Z as req,ee as mount,A as h,H as fx,Y as each,J as batch,G as $$,S as $}; +var m=(e)=>typeof e==="function",j=(e)=>e&&typeof e==="object",N=Array.isArray,E=typeof document<"u"?document:null,V=(e)=>e?._isRuntime?e.container:e instanceof Node?e:E.createTextNode(e==null?"":String(e)),d=null,_=null,b=!1,C=0,R=new Set,I=new WeakMap,O=Symbol("iter"),L=new WeakMap,K="http://www.w3.org/2000/svg",P="http://www.w3.org/1999/xlink",W=new Set("svg,path,circle,rect,line,polyline,polygon,g,defs,text,textPath,tspan,use,symbol,image,marker,ellipse".split(",")),v=null,q=(e)=>{v=e},S=(e)=>{if(!e||e._disposed)return;e._disposed=!0;let r=[e];while(r.length){let n=r.pop();if(n._cleanups)n._cleanups.forEach((o)=>o()),n._cleanups.clear();if(n._children)n._children.forEach((o)=>r.push(o)),n._children.clear();if(n._deps)n._deps.forEach((o)=>o.delete(n)),n._deps.clear()}},g=(e)=>{if(_)(_._cleanups||=new Set).add(e)},D=(e)=>{let r=d;d=null;try{return e()}finally{d=r}},x=(e,r=!1)=>{let n=()=>{if(n._disposed)return;if(n._deps)n._deps.forEach((c)=>c.delete(n));if(n._cleanups)n._cleanups.forEach((c)=>c()),n._cleanups.clear();let o=d,s=_;d=_=n;try{return n._result=e()}catch(c){console.error("[SigPro]",c)}finally{d=o,_=s}};if(n._deps=n._cleanups=n._children=null,n._disposed=!1,n._isComputed=r,n._depth=d?d._depth+1:0,n._mounts=[],n._parent=_,_)(_._children||=new Set).add(n);return n},$=()=>{if(b)return;b=!0;let e=Array.from(R).sort((r,n)=>r._depth-n._depth);R.clear();for(let r of e)if(!r._disposed)r();b=!1},z=(e)=>{C++;try{return e()}finally{if(C--,C===0&&R.size>0&&!b)$()}},w=(e,r=!1)=>{if(!r&&d&&!d._disposed)e.add(d),(d._deps||=new Set).add(e);else if(r&&e.size>0){let n=!1;for(let o of e){if(o===d||o._disposed)continue;if(o._isComputed){if(o._dirty=!0,o._subs)w(o._subs,!0)}else R.add(o),n=!0}if(n&&!b&&C===0)queueMicrotask($)}},U=(e,r=null)=>{let n=new Set;if(m(e)){let o,s=()=>{if(s._dirty){let c=d;d=s;try{let t=e();if(!Object.is(o,t))o=t,w(n,!0)}finally{d=c}s._dirty=!1}return w(n),o};return s._isComputed=!0,s._subs=n,s._dirty=!0,s._deps=null,s._disposed=!1,s}if(r)try{e=JSON.parse(localStorage.getItem(r))??e}catch(o){}return(...o)=>{if(o.length){let s=m(o[0])?o[0](e):o[0];if(!Object.is(e,s)){if(e=s,r)localStorage.setItem(r,JSON.stringify(e));w(n,!0)}}return w(n),e}},X=(e)=>{if(!j(e))return e;let r=I.get(e);if(r)return r;let n=new Map,o=(c)=>{let t=n.get(c);if(!t)n.set(c,t=new Set);return t},s=new Proxy(e,{get(c,t,i){if(typeof t!=="symbol")w(o(t));return X(Reflect.get(c,t,i))},set(c,t,i,l){let u=Reflect.has(c,t),a=Reflect.get(c,t,l),f=Reflect.set(c,t,i,l);if(f&&!Object.is(a,i)){if(w(o(t),!0),!u)w(o(O),!0)}return f},deleteProperty(c,t){let i=Reflect.deleteProperty(c,t);if(i)w(o(t),!0),w(o(O),!0);return i},ownKeys(c){return w(o(O)),Reflect.ownKeys(c)}});return I.set(e,s),s},k=(e,r)=>{if(r===void 0){let o=x(e);return o(),()=>S(o)}let n=x(()=>{let o=Array.isArray(e)?e.map((s)=>s()):e();D(()=>r(o))});return n(),()=>S(n)},B=(e)=>{if(!e)return;if(e._cleanups)e._cleanups.forEach((r)=>r()),e._cleanups.clear();if(e._ownerEffect)S(e._ownerEffect);if(e.childNodes)e.childNodes.forEach((r)=>B(r))},M=(e,r={},n=[])=>{if(r instanceof Node||N(r)||!j(r))n=r,r={};if(m(e)){let t=x(()=>{let a=e(r,{children:n,emit:(f,...h)=>r[`on${f[0].toUpperCase()}${f.slice(1)}`]?.(...h)});return t._result=a,a});t();let i=t._result;if(i==null)return null;let l=i instanceof Node||N(i)&&i.every((a)=>a instanceof Node)?i:E.createTextNode(String(i)),u=(a)=>{if(j(a)&&!a._isRuntime)a._mounts=t._mounts||[],a._cleanups=t._cleanups||new Set,a._ownerEffect=t};return N(l)?l.forEach(u):u(l),l}let o=W.has(e),s=o?E.createElementNS(K,e):E.createElement(e);s._cleanups=new Set;for(let t of Object.keys(r)){let i=r[t];if(t==="ref"){m(i)?i(s):i.current=s;continue}let l=v?v(t,i):i;if(o&&t.startsWith("xlink:")){l==null?s.removeAttributeNS(P,t.slice(6)):s.setAttributeNS(P,t.slice(6),l);continue}if(t.startsWith("on")){let u=t.slice(2).toLowerCase();s.addEventListener(u,l);let a=()=>s.removeEventListener(u,l);s._cleanups.add(a),g(a)}else if(m(l)){let u=x(()=>{let a=l(),f=v?v(t,a):a;if(t==="class")s.className=f||"";else if(f==null)s.removeAttribute(t);else if(t in s&&!o)s[t]=f;else s.setAttribute(t,f===!0?"":f)});if(u(),s._cleanups.add(()=>S(u)),g(()=>S(u)),/^(INPUT|TEXTAREA|SELECT)$/.test(s.tagName)&&(t==="value"||t==="checked")){let a=t==="checked"?"change":"input";s.addEventListener(a,(f)=>l(f.target[t]))}}else if(l!=null)if(t in s&&!o)s[t]=l;else s.setAttribute(t,l===!0?"":l)}let c=(t)=>{if(N(t))return t.forEach(c);if(m(t)){let i=E.createTextNode("");s.appendChild(i);let l=[],u=x(()=>{let a=t(),f=(N(a)?a:[a]).map(V);l.forEach((p)=>{if(p._isRuntime)p.destroy();else B(p);if(p.parentNode)p.remove()});let h=i;for(let p=f.length-1;p>=0;p--){let y=f[p];if(y.parentNode!==h.parentNode)h.parentNode?.insertBefore(y,h);if(y._mounts)y._mounts.forEach((G)=>G());h=y}l=f});u(),s._cleanups.add(()=>S(u)),g(()=>S(u))}else{let i=V(t);if(s.appendChild(i),i._mounts)i._mounts.forEach((l)=>l())}};return c(n),s},T=(e)=>{let r=new Set,n=_,o=d,s=E.createElement("div");s.style.display="contents",s.setAttribute("role","presentation"),_={_cleanups:r},d=null;let c=(t)=>{if(!t)return;if(t._isRuntime)r.add(t.destroy),s.appendChild(t.container);else if(N(t))t.forEach(c);else s.appendChild(t instanceof Node?t:E.createTextNode(String(t==null?"":t)))};try{c(e({onCleanup:(t)=>r.add(t)}))}finally{_=n,d=o}return{_isRuntime:!0,container:s,destroy:()=>{r.forEach((t)=>t()),B(s),s.remove()}}},F=(e,r,n=null)=>{let o=E.createTextNode(""),s=M("div",{style:"display:contents"},[o]),c=null;return k(()=>!!(m(e)?e():e),(t)=>{if(c)c.destroy(),c=null;let i=t?r:n;if(i)c=T(()=>m(i)?i():i),s.insertBefore(c.container,o)}),g(()=>c?.destroy()),s},J=(e,r,n)=>{let o=E.createTextNode(""),s=M("div",{style:"display:contents"},[o]),c=new Map;return k(()=>(m(e)?e():e)||[],(t)=>{let i=new Map,l=[],u=t||[];for(let f=0;fr(h,f));else c.delete(p);i.set(p,y),l.push(y)}c.forEach((f)=>f.destroy());let a=o;for(let f=l.length-1;f>=0;f--){let p=l[f].container;if(p.nextSibling!==a)s.insertBefore(p,a);a=p}c=i}),s},A=(e)=>{let r=()=>window.location.hash.slice(1)||"/",n=U(r()),o=()=>n(r());window.addEventListener("hashchange",o),g(()=>window.removeEventListener("hashchange",o));let s=M("div",{class:"router-hook"}),c=null;return k([n],()=>{let t=n(),i=e.find((l)=>{let u=l.path.split("/").filter(Boolean),a=t.split("/").filter(Boolean);return u.length===a.length&&u.every((f,h)=>f[0]===":"||f===a[h])})||e.find((l)=>l.path==="*");if(i){c?.destroy();let l={};i.path.split("/").filter(Boolean).forEach((u,a)=>{if(u[0]===":")l[u.slice(1)]=t.split("/").filter(Boolean)[a]}),A.params(l),c=T(()=>m(i.component)?i.component(l):i.component),s.replaceChildren(c.container)}}),s};A.params=U({});A.to=(e)=>window.location.hash=e.replace(/^#?\/?/,"#/");A.back=()=>window.history.back();A.path=()=>window.location.hash.replace(/^#/,"")||"/";var Q=(e,r)=>{let n=typeof r==="string"?E.querySelector(r):r;if(!n)return;if(L.has(n))L.get(n).destroy();let o=T(m(e)?e:()=>e);return n.replaceChildren(o.container),L.set(n,o),o};export{F as when,k as watch,A as router,Q as mount,M as h,q as filterXSS,J as each,z as batch,X as $$,U as $}; diff --git a/dist/sigpro.js b/dist/sigpro.js index 85b62a5..3bf3388 100644 --- a/dist/sigpro.js +++ b/dist/sigpro.js @@ -37,17 +37,15 @@ }); }; - // index.js - var exports_sigpro = {}; - __export(exports_sigpro, { + // sigpro-full.js + var exports_sigpro_full = {}; + __export(exports_sigpro_full, { when: () => when, watch: () => watch, - sigpro: () => sigpro, router: () => router, - req: () => req, mount: () => mount, h: () => h, - fx: () => fx, + filterXSS: () => filterXSS, each: () => each, batch: () => batch, $$: () => $$, @@ -71,6 +69,10 @@ var SVG_NS = "http://www.w3.org/2000/svg"; var XLINK_NS = "http://www.w3.org/1999/xlink"; var SVG_TAGS = new Set("svg,path,circle,rect,line,polyline,polygon,g,defs,text,textPath,tspan,use,symbol,image,marker,ellipse".split(",")); + var attrFilter = null; + var filterXSS = (fn) => { + attrFilter = fn; + }; var dispose = (eff) => { if (!eff || eff._disposed) return; @@ -299,21 +301,6 @@ if (node.childNodes) node.childNodes.forEach((n) => cleanupNode(n)); }; - var DANGEROUS_PROTOCOL = /^\s*(javascript|data|vbscript):/i; - var DANGEROUS_URI_ATTRS = new Set(["src", "href", "formaction", "action", "background", "code", "archive"]); - var isDangerousAttr = (key) => DANGEROUS_URI_ATTRS.has(key) || key.startsWith("on"); - var validateAttr = (key, val) => { - if (val == null || val === false) - return null; - if (isDangerousAttr(key)) { - const sVal = String(val); - if (DANGEROUS_PROTOCOL.test(sVal)) { - console.warn(`[SigPro] Bloqueado protocolo peligroso en ${key}`); - return "#"; - } - } - return val; - }; var h = (tag, props = {}, children = []) => { if (props instanceof Node || isArr(props) || !isObj(props)) { children = props; @@ -352,38 +339,38 @@ isFunc(v) ? v(el) : v.current = el; continue; } + let val = attrFilter ? attrFilter(k, v) : v; if (isSVG && k.startsWith("xlink:")) { - const cleanVal = validateAttr(k.slice(6), v); - cleanVal == null ? el.removeAttributeNS(XLINK_NS, k.slice(6)) : el.setAttributeNS(XLINK_NS, k.slice(6), cleanVal); + val == null ? el.removeAttributeNS(XLINK_NS, k.slice(6)) : el.setAttributeNS(XLINK_NS, k.slice(6), val); continue; } if (k.startsWith("on")) { const ev = k.slice(2).toLowerCase(); - el.addEventListener(ev, v); - const off = () => el.removeEventListener(ev, v); + el.addEventListener(ev, val); + const off = () => el.removeEventListener(ev, val); el._cleanups.add(off); onUnmount(off); - } else if (isFunc(v)) { + } else if (isFunc(val)) { const effect = createEffect(() => { - const val = validateAttr(k, v()); + const raw = val(); + const safeVal = attrFilter ? attrFilter(k, raw) : raw; if (k === "class") - el.className = val || ""; - else if (val == null) + el.className = safeVal || ""; + else if (safeVal == null) el.removeAttribute(k); else if (k in el && !isSVG) - el[k] = val; + el[k] = safeVal; else - el.setAttribute(k, val === true ? "" : val); + el.setAttribute(k, safeVal === true ? "" : safeVal); }); effect(); el._cleanups.add(() => dispose(effect)); onUnmount(() => dispose(effect)); if (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) && (k === "value" || k === "checked")) { const evType = k === "checked" ? "change" : "input"; - el.addEventListener(evType, (ev) => v(ev.target[k])); + el.addEventListener(evType, (ev) => val(ev.target[k])); } } else { - const val = validateAttr(k, v); if (val != null) { if (k in el && !isSVG) el[k] = val; @@ -489,35 +476,6 @@ onUnmount(() => currentView?.destroy()); return root; }; - var fx = ({ name, duration = 200, scale, slide, rotate, blur }, child) => { - const el = typeof child === "function" ? child() : child; - if (!(el instanceof Node)) - return el; - if (name) { - el.style.animation = `${name}-in ${duration}ms`; - return el; - } - const hasTransform = scale || slide || rotate || blur; - const initialTransform = [ - scale ? "scale(0.95)" : "", - slide ? "translateY(-10px)" : "", - rotate ? "rotate(-2deg)" : "" - ].filter(Boolean).join(" "); - el.style.transition = `all ${duration}ms ease`; - el.style.opacity = "0"; - if (hasTransform) - el.style.transform = initialTransform; - if (blur) - el.style.filter = "blur(4px)"; - requestAnimationFrame(() => { - el.style.opacity = "1"; - if (hasTransform) - el.style.transform = "none"; - if (blur) - el.style.filter = "none"; - }); - return el; - }; var each = (src, itemFn, keyField) => { const anchor = doc.createTextNode(""); const root = h("div", { style: "display:contents" }, [anchor]); @@ -583,47 +541,6 @@ router.to = (p) => window.location.hash = p.replace(/^#?\/?/, "#/"); router.back = () => window.history.back(); router.path = () => window.location.hash.replace(/^#/, "") || "/"; - var req = ({ url, method = "GET", headers = {} }) => { - const loading = $(false); - const error = $(null); - const data = $(null); - let controller = null; - let timeoutId = null; - const run = async (body = null) => { - controller?.abort(); - clearTimeout(timeoutId); - controller = new AbortController; - timeoutId = setTimeout(() => controller.abort(), 1e4); - loading(true); - error(null); - try { - const isFormData = body instanceof FormData; - const res = await fetch(url, { - method, - headers: isFormData ? headers : { "Content-Type": "application/json", ...headers }, - body: isFormData ? body : body ? JSON.stringify(body) : undefined, - signal: controller.signal - }); - const text = await res.text(); - const json = text ? JSON.parse(text) : null; - if (!res.ok) - throw new Error(json?.message || res.statusText); - data(json); - return json; - } catch (e) { - if (e.name !== "AbortError") - error(e.message); - throw e; - } finally { - loading(false); - clearTimeout(timeoutId); - controller = null; - timeoutId = null; - } - }; - const abort = () => controller?.abort(); - return { run, abort, loading, error, data }; - }; var mount = (comp, target) => { const t = typeof target === "string" ? doc.querySelector(target) : target; if (!t) @@ -635,16 +552,38 @@ MOUNTED_NODES.set(t, inst); return inst; }; - var sigproFn = Object.freeze({ $, $$, watch, h, when, each, fx, router, req, mount, batch }); - var sigpro = () => { - if (typeof window !== "undefined") { - Object.assign(window, sigproFn); - "a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video".split(" ").forEach((tag) => { - window[tag] = (props, children) => h(tag, props, children); - }); - console.log("SigPro DX installed."); + // sigpro/tags.js + if (typeof window !== "undefined") { + "a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video".split(" ").forEach((tag) => { + window[tag] = (props, children) => h(tag, props, children); + }); + console.log("SigPro tags ready"); + } + + // sigpro/xss.js + var DANGEROUS_PROTOCOL = /^\s*(javascript|data|vbscript):/i; + var DANGEROUS_URI_ATTRS = new Set(["src", "href", "formaction", "action", "background", "code", "archive"]); + var isDangerousAttr = (key) => DANGEROUS_URI_ATTRS.has(key) || key.startsWith("on"); + var validateAttr = (key, val) => { + if (val == null || val === false) + return null; + if (isDangerousAttr(key)) { + const sVal = String(val); + if (DANGEROUS_PROTOCOL.test(sVal)) { + console.warn(`[SigPro XSS] Locked ${key}`); + return "#"; + } } + return val; }; - if (typeof import.meta === "undefined" && typeof window !== "undefined") - sigpro(); + filterXSS(validateAttr); + + // sigpro-full.js + if (typeof window !== "undefined") { + const props = {}; + for (const fn of [["$", $], ["$$", $$], ["watch", watch], ["h", h], ["when", when], ["each", each], ["router", router], ["mount", mount], ["batch", batch]]) { + props[fn[0]] = { value: fn[1], writable: false, configurable: false, enumerable: true }; + } + Object.defineProperties(window, props); + } })(); diff --git a/dist/sigpro.min.js b/dist/sigpro.min.js index 05e4a3a..a1d99cd 100644 --- a/dist/sigpro.min.js +++ b/dist/sigpro.min.js @@ -1 +1 @@ -(()=>{var{defineProperty:k,getOwnPropertyNames:Y,getOwnPropertyDescriptor:Z}=Object,ee=Object.prototype.hasOwnProperty;function te(e){return this[e]}var ne=(e)=>{var o=(U??=new WeakMap).get(e),n;if(o)return o;if(o=k({},"__esModule",{value:!0}),e&&typeof e==="object"||typeof e==="function"){for(var s of Y(e))if(!ee.call(o,s))k(o,s,{get:te.bind(e,s),enumerable:!(n=Z(e,s))||n.enumerable})}return U.set(e,o),o},U;var oe=(e)=>e;function se(e,o){this[e]=oe.bind(null,o)}var re=(e,o)=>{for(var n in o)k(e,n,{get:o[n],enumerable:!0,configurable:!0,set:se.bind(o,n)})};var pe={};re(pe,{when:()=>W,watch:()=>O,sigpro:()=>Q,router:()=>S,req:()=>z,mount:()=>X,h:()=>v,fx:()=>J,each:()=>K,batch:()=>F,$$:()=>P,$:()=>b});var y=(e)=>typeof e==="function",L=(e)=>e&&typeof e==="object",N=Array.isArray,g=typeof document<"u"?document:null,V=(e)=>e?._isRuntime?e.container:e instanceof Node?e:g.createTextNode(e==null?"":String(e)),p=null,_=null,x=!1,R=0,C=new Set,G=new WeakMap,$=Symbol("iter"),B=new WeakMap,ie="http://www.w3.org/2000/svg",M="http://www.w3.org/1999/xlink",ce=new Set("svg,path,circle,rect,line,polyline,polygon,g,defs,text,textPath,tspan,use,symbol,image,marker,ellipse".split(",")),E=(e)=>{if(!e||e._disposed)return;e._disposed=!0;let o=[e];while(o.length){let n=o.pop();if(n._cleanups)n._cleanups.forEach((s)=>s()),n._cleanups.clear();if(n._children)n._children.forEach((s)=>o.push(s)),n._children.clear();if(n._deps)n._deps.forEach((s)=>s.delete(n)),n._deps.clear()}},T=(e)=>{if(_)(_._cleanups||=new Set).add(e)},le=(e)=>{let o=p;p=null;try{return e()}finally{p=o}},A=(e,o=!1)=>{let n=()=>{if(n._disposed)return;if(n._deps)n._deps.forEach((c)=>c.delete(n));if(n._cleanups)n._cleanups.forEach((c)=>c()),n._cleanups.clear();let s=p,r=_;p=_=n;try{return n._result=e()}catch(c){console.error("[SigPro]",c)}finally{p=s,_=r}};if(n._deps=n._cleanups=n._children=null,n._disposed=!1,n._isComputed=o,n._depth=p?p._depth+1:0,n._mounts=[],n._parent=_,_)(_._children||=new Set).add(n);return n},q=()=>{if(x)return;x=!0;let e=Array.from(C).sort((o,n)=>o._depth-n._depth);C.clear();for(let o of e)if(!o._disposed)o();x=!1},F=(e)=>{R++;try{return e()}finally{if(R--,R===0&&C.size>0&&!x)q()}},w=(e,o=!1)=>{if(!o&&p&&!p._disposed)e.add(p),(p._deps||=new Set).add(e);else if(o&&e.size>0){let n=!1;for(let s of e){if(s===p||s._disposed)continue;if(s._isComputed){if(s._dirty=!0,s._subs)w(s._subs,!0)}else C.add(s),n=!0}if(n&&!x&&R===0)queueMicrotask(q)}},b=(e,o=null)=>{let n=new Set;if(y(e)){let s,r=()=>{if(r._dirty){let c=p;p=r;try{let t=e();if(!Object.is(s,t))s=t,w(n,!0)}finally{p=c}r._dirty=!1}return w(n),s};return r._isComputed=!0,r._subs=n,r._dirty=!0,r._deps=null,r._disposed=!1,r}if(o)try{e=JSON.parse(localStorage.getItem(o))??e}catch(s){}return(...s)=>{if(s.length){let r=y(s[0])?s[0](e):s[0];if(!Object.is(e,r)){if(e=r,o)localStorage.setItem(o,JSON.stringify(e));w(n,!0)}}return w(n),e}},P=(e)=>{if(!L(e))return e;let o=G.get(e);if(o)return o;let n=new Map,s=(c)=>{let t=n.get(c);if(!t)n.set(c,t=new Set);return t},r=new Proxy(e,{get(c,t,i){if(typeof t!=="symbol")w(s(t));return P(Reflect.get(c,t,i))},set(c,t,i,l){let u=Reflect.has(c,t),a=Reflect.get(c,t,l),f=Reflect.set(c,t,i,l);if(f&&!Object.is(a,i)){if(w(s(t),!0),!u)w(s($),!0)}return f},deleteProperty(c,t){let i=Reflect.deleteProperty(c,t);if(i)w(s(t),!0),w(s($),!0);return i},ownKeys(c){return w(s($)),Reflect.ownKeys(c)}});return G.set(e,r),r},O=(e,o)=>{if(o===void 0){let s=A(e);return s(),()=>E(s)}let n=A(()=>{let s=Array.isArray(e)?e.map((r)=>r()):e();le(()=>o(s))});return n(),()=>E(n)},I=(e)=>{if(!e)return;if(e._cleanups)e._cleanups.forEach((o)=>o()),e._cleanups.clear();if(e._ownerEffect)E(e._ownerEffect);if(e.childNodes)e.childNodes.forEach((o)=>I(o))},ae=/^\s*(javascript|data|vbscript):/i,fe=new Set(["src","href","formaction","action","background","code","archive"]),ue=(e)=>fe.has(e)||e.startsWith("on"),D=(e,o)=>{if(o==null||o===!1)return null;if(ue(e)){let n=String(o);if(ae.test(n))return console.warn(`[SigPro] Bloqueado protocolo peligroso en ${e}`),"#"}return o},v=(e,o={},n=[])=>{if(o instanceof Node||N(o)||!L(o))n=o,o={};if(y(e)){let t=A(()=>{let a=e(o,{children:n,emit:(f,...h)=>o[`on${f[0].toUpperCase()}${f.slice(1)}`]?.(...h)});return t._result=a,a});t();let i=t._result;if(i==null)return null;let l=i instanceof Node||N(i)&&i.every((a)=>a instanceof Node)?i:g.createTextNode(String(i)),u=(a)=>{if(L(a)&&!a._isRuntime)a._mounts=t._mounts||[],a._cleanups=t._cleanups||new Set,a._ownerEffect=t};return N(l)?l.forEach(u):u(l),l}let s=ce.has(e),r=s?g.createElementNS(ie,e):g.createElement(e);r._cleanups=new Set;for(let t of Object.keys(o)){let i=o[t];if(t==="ref"){y(i)?i(r):i.current=r;continue}if(s&&t.startsWith("xlink:")){let l=D(t.slice(6),i);l==null?r.removeAttributeNS(M,t.slice(6)):r.setAttributeNS(M,t.slice(6),l);continue}if(t.startsWith("on")){let l=t.slice(2).toLowerCase();r.addEventListener(l,i);let u=()=>r.removeEventListener(l,i);r._cleanups.add(u),T(u)}else if(y(i)){let l=A(()=>{let u=D(t,i());if(t==="class")r.className=u||"";else if(u==null)r.removeAttribute(t);else if(t in r&&!s)r[t]=u;else r.setAttribute(t,u===!0?"":u)});if(l(),r._cleanups.add(()=>E(l)),T(()=>E(l)),/^(INPUT|TEXTAREA|SELECT)$/.test(r.tagName)&&(t==="value"||t==="checked")){let u=t==="checked"?"change":"input";r.addEventListener(u,(a)=>i(a.target[t]))}}else{let l=D(t,i);if(l!=null)if(t in r&&!s)r[t]=l;else r.setAttribute(t,l===!0?"":l)}}let c=(t)=>{if(N(t))return t.forEach(c);if(y(t)){let i=g.createTextNode("");r.appendChild(i);let l=[],u=A(()=>{let a=t(),f=(N(a)?a:[a]).map(V);l.forEach((d)=>{if(d._isRuntime)d.destroy();else I(d);if(d.parentNode)d.remove()});let h=i;for(let d=f.length-1;d>=0;d--){let m=f[d];if(m.parentNode!==h.parentNode)h.parentNode?.insertBefore(m,h);if(m._mounts)m._mounts.forEach((H)=>H());h=m}l=f});u(),r._cleanups.add(()=>E(u)),T(()=>E(u))}else{let i=V(t);if(r.appendChild(i),i._mounts)i._mounts.forEach((l)=>l())}};return c(n),r},j=(e)=>{let o=new Set,n=_,s=p,r=g.createElement("div");r.style.display="contents",r.setAttribute("role","presentation"),_={_cleanups:o},p=null;let c=(t)=>{if(!t)return;if(t._isRuntime)o.add(t.destroy),r.appendChild(t.container);else if(N(t))t.forEach(c);else r.appendChild(t instanceof Node?t:g.createTextNode(String(t==null?"":t)))};try{c(e({onCleanup:(t)=>o.add(t)}))}finally{_=n,p=s}return{_isRuntime:!0,container:r,destroy:()=>{o.forEach((t)=>t()),I(r),r.remove()}}},W=(e,o,n=null)=>{let s=g.createTextNode(""),r=v("div",{style:"display:contents"},[s]),c=null;return O(()=>!!(y(e)?e():e),(t)=>{if(c)c.destroy(),c=null;let i=t?o:n;if(i)c=j(()=>y(i)?i():i),r.insertBefore(c.container,s)}),T(()=>c?.destroy()),r},J=({name:e,duration:o=200,scale:n,slide:s,rotate:r,blur:c},t)=>{let i=typeof t==="function"?t():t;if(!(i instanceof Node))return i;if(e)return i.style.animation=`${e}-in ${o}ms`,i;let l=n||s||r||c,u=[n?"scale(0.95)":"",s?"translateY(-10px)":"",r?"rotate(-2deg)":""].filter(Boolean).join(" ");if(i.style.transition=`all ${o}ms ease`,i.style.opacity="0",l)i.style.transform=u;if(c)i.style.filter="blur(4px)";return requestAnimationFrame(()=>{if(i.style.opacity="1",l)i.style.transform="none";if(c)i.style.filter="none"}),i},K=(e,o,n)=>{let s=g.createTextNode(""),r=v("div",{style:"display:contents"},[s]),c=new Map;return O(()=>(y(e)?e():e)||[],(t)=>{let i=new Map,l=[],u=t||[];for(let f=0;fo(h,f));else c.delete(d);i.set(d,m),l.push(m)}c.forEach((f)=>f.destroy());let a=s;for(let f=l.length-1;f>=0;f--){let d=l[f].container;if(d.nextSibling!==a)r.insertBefore(d,a);a=d}c=i}),r},S=(e)=>{let o=()=>window.location.hash.slice(1)||"/",n=b(o()),s=()=>n(o());window.addEventListener("hashchange",s),T(()=>window.removeEventListener("hashchange",s));let r=v("div",{class:"router-hook"}),c=null;return O([n],()=>{let t=n(),i=e.find((l)=>{let u=l.path.split("/").filter(Boolean),a=t.split("/").filter(Boolean);return u.length===a.length&&u.every((f,h)=>f[0]===":"||f===a[h])})||e.find((l)=>l.path==="*");if(i){c?.destroy();let l={};i.path.split("/").filter(Boolean).forEach((u,a)=>{if(u[0]===":")l[u.slice(1)]=t.split("/").filter(Boolean)[a]}),S.params(l),c=j(()=>y(i.component)?i.component(l):i.component),r.replaceChildren(c.container)}}),r};S.params=b({});S.to=(e)=>window.location.hash=e.replace(/^#?\/?/,"#/");S.back=()=>window.history.back();S.path=()=>window.location.hash.replace(/^#/,"")||"/";var z=({url:e,method:o="GET",headers:n={}})=>{let s=b(!1),r=b(null),c=b(null),t=null,i=null;return{run:async(a=null)=>{t?.abort(),clearTimeout(i),t=new AbortController,i=setTimeout(()=>t.abort(),1e4),s(!0),r(null);try{let f=a instanceof FormData,h=await fetch(e,{method:o,headers:f?n:{"Content-Type":"application/json",...n},body:f?a:a?JSON.stringify(a):void 0,signal:t.signal}),d=await h.text(),m=d?JSON.parse(d):null;if(!h.ok)throw Error(m?.message||h.statusText);return c(m),m}catch(f){if(f.name!=="AbortError")r(f.message);throw f}finally{s(!1),clearTimeout(i),t=null,i=null}},abort:()=>t?.abort(),loading:s,error:r,data:c}},X=(e,o)=>{let n=typeof o==="string"?g.querySelector(o):o;if(!n)return;if(B.has(n))B.get(n).destroy();let s=j(y(e)?e:()=>e);return n.replaceChildren(s.container),B.set(n,s),s},de=Object.freeze({$:b,$$:P,watch:O,h:v,when:W,each:K,fx:J,router:S,req:z,mount:X,batch:F}),Q=()=>{if(typeof window<"u")Object.assign(window,de),"a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video".split(" ").forEach((e)=>{window[e]=(o,n)=>v(e,o,n)}),console.log("SigPro DX installed.")};if(typeof import.meta>"u"&&typeof window<"u")Q();})(); +(()=>{var{defineProperty:L,getOwnPropertyNames:Q,getOwnPropertyDescriptor:H}=Object,Y=Object.prototype.hasOwnProperty;function Z(e){return this[e]}var ee=(e)=>{var t=(X??=new WeakMap).get(e),n;if(t)return t;if(t=L({},"__esModule",{value:!0}),e&&typeof e==="object"||typeof e==="function"){for(var s of Q(e))if(!Y.call(t,s))L(t,s,{get:Z.bind(e,s),enumerable:!(n=H(e,s))||n.enumerable})}return X.set(e,t),t},X;var te=(e)=>e;function ne(e,t){this[e]=te.bind(null,t)}var oe=(e,t)=>{for(var n in t)L(e,n,{get:t[n],enumerable:!0,configurable:!0,set:ne.bind(t,n)})};var ue={};oe(ue,{when:()=>D,watch:()=>v,router:()=>E,mount:()=>W,h:()=>b,filterXSS:()=>B,each:()=>G,batch:()=>I,$$:()=>j,$:()=>T});var w=(e)=>typeof e==="function",V=(e)=>e&&typeof e==="object",N=Array.isArray,S=typeof document<"u"?document:null,K=(e)=>e?._isRuntime?e.container:e instanceof Node?e:S.createTextNode(e==null?"":String(e)),d=null,_=null,A=!1,C=0,$=new Set,q=new WeakMap,P=Symbol("iter"),U=new WeakMap,se="http://www.w3.org/2000/svg",z="http://www.w3.org/1999/xlink",re=new Set("svg,path,circle,rect,line,polyline,polygon,g,defs,text,textPath,tspan,use,symbol,image,marker,ellipse".split(",")),x=null,B=(e)=>{x=e},g=(e)=>{if(!e||e._disposed)return;e._disposed=!0;let t=[e];while(t.length){let n=t.pop();if(n._cleanups)n._cleanups.forEach((s)=>s()),n._cleanups.clear();if(n._children)n._children.forEach((s)=>t.push(s)),n._children.clear();if(n._deps)n._deps.forEach((s)=>s.delete(n)),n._deps.clear()}},R=(e)=>{if(_)(_._cleanups||=new Set).add(e)},ce=(e)=>{let t=d;d=null;try{return e()}finally{d=t}},O=(e,t=!1)=>{let n=()=>{if(n._disposed)return;if(n._deps)n._deps.forEach((c)=>c.delete(n));if(n._cleanups)n._cleanups.forEach((c)=>c()),n._cleanups.clear();let s=d,r=_;d=_=n;try{return n._result=e()}catch(c){console.error("[SigPro]",c)}finally{d=s,_=r}};if(n._deps=n._cleanups=n._children=null,n._disposed=!1,n._isComputed=t,n._depth=d?d._depth+1:0,n._mounts=[],n._parent=_,_)(_._children||=new Set).add(n);return n},F=()=>{if(A)return;A=!0;let e=Array.from($).sort((t,n)=>t._depth-n._depth);$.clear();for(let t of e)if(!t._disposed)t();A=!1},I=(e)=>{C++;try{return e()}finally{if(C--,C===0&&$.size>0&&!A)F()}},m=(e,t=!1)=>{if(!t&&d&&!d._disposed)e.add(d),(d._deps||=new Set).add(e);else if(t&&e.size>0){let n=!1;for(let s of e){if(s===d||s._disposed)continue;if(s._isComputed){if(s._dirty=!0,s._subs)m(s._subs,!0)}else $.add(s),n=!0}if(n&&!A&&C===0)queueMicrotask(F)}},T=(e,t=null)=>{let n=new Set;if(w(e)){let s,r=()=>{if(r._dirty){let c=d;d=r;try{let o=e();if(!Object.is(s,o))s=o,m(n,!0)}finally{d=c}r._dirty=!1}return m(n),s};return r._isComputed=!0,r._subs=n,r._dirty=!0,r._deps=null,r._disposed=!1,r}if(t)try{e=JSON.parse(localStorage.getItem(t))??e}catch(s){}return(...s)=>{if(s.length){let r=w(s[0])?s[0](e):s[0];if(!Object.is(e,r)){if(e=r,t)localStorage.setItem(t,JSON.stringify(e));m(n,!0)}}return m(n),e}},j=(e)=>{if(!V(e))return e;let t=q.get(e);if(t)return t;let n=new Map,s=(c)=>{let o=n.get(c);if(!o)n.set(c,o=new Set);return o},r=new Proxy(e,{get(c,o,i){if(typeof o!=="symbol")m(s(o));return j(Reflect.get(c,o,i))},set(c,o,i,l){let u=Reflect.has(c,o),a=Reflect.get(c,o,l),f=Reflect.set(c,o,i,l);if(f&&!Object.is(a,i)){if(m(s(o),!0),!u)m(s(P),!0)}return f},deleteProperty(c,o){let i=Reflect.deleteProperty(c,o);if(i)m(s(o),!0),m(s(P),!0);return i},ownKeys(c){return m(s(P)),Reflect.ownKeys(c)}});return q.set(e,r),r},v=(e,t)=>{if(t===void 0){let s=O(e);return s(),()=>g(s)}let n=O(()=>{let s=Array.isArray(e)?e.map((r)=>r()):e();ce(()=>t(s))});return n(),()=>g(n)},M=(e)=>{if(!e)return;if(e._cleanups)e._cleanups.forEach((t)=>t()),e._cleanups.clear();if(e._ownerEffect)g(e._ownerEffect);if(e.childNodes)e.childNodes.forEach((t)=>M(t))},b=(e,t={},n=[])=>{if(t instanceof Node||N(t)||!V(t))n=t,t={};if(w(e)){let o=O(()=>{let a=e(t,{children:n,emit:(f,...h)=>t[`on${f[0].toUpperCase()}${f.slice(1)}`]?.(...h)});return o._result=a,a});o();let i=o._result;if(i==null)return null;let l=i instanceof Node||N(i)&&i.every((a)=>a instanceof Node)?i:S.createTextNode(String(i)),u=(a)=>{if(V(a)&&!a._isRuntime)a._mounts=o._mounts||[],a._cleanups=o._cleanups||new Set,a._ownerEffect=o};return N(l)?l.forEach(u):u(l),l}let s=re.has(e),r=s?S.createElementNS(se,e):S.createElement(e);r._cleanups=new Set;for(let o of Object.keys(t)){let i=t[o];if(o==="ref"){w(i)?i(r):i.current=r;continue}let l=x?x(o,i):i;if(s&&o.startsWith("xlink:")){l==null?r.removeAttributeNS(z,o.slice(6)):r.setAttributeNS(z,o.slice(6),l);continue}if(o.startsWith("on")){let u=o.slice(2).toLowerCase();r.addEventListener(u,l);let a=()=>r.removeEventListener(u,l);r._cleanups.add(a),R(a)}else if(w(l)){let u=O(()=>{let a=l(),f=x?x(o,a):a;if(o==="class")r.className=f||"";else if(f==null)r.removeAttribute(o);else if(o in r&&!s)r[o]=f;else r.setAttribute(o,f===!0?"":f)});if(u(),r._cleanups.add(()=>g(u)),R(()=>g(u)),/^(INPUT|TEXTAREA|SELECT)$/.test(r.tagName)&&(o==="value"||o==="checked")){let a=o==="checked"?"change":"input";r.addEventListener(a,(f)=>l(f.target[o]))}}else if(l!=null)if(o in r&&!s)r[o]=l;else r.setAttribute(o,l===!0?"":l)}let c=(o)=>{if(N(o))return o.forEach(c);if(w(o)){let i=S.createTextNode("");r.appendChild(i);let l=[],u=O(()=>{let a=o(),f=(N(a)?a:[a]).map(K);l.forEach((p)=>{if(p._isRuntime)p.destroy();else M(p);if(p.parentNode)p.remove()});let h=i;for(let p=f.length-1;p>=0;p--){let y=f[p];if(y.parentNode!==h.parentNode)h.parentNode?.insertBefore(y,h);if(y._mounts)y._mounts.forEach((J)=>J());h=y}l=f});u(),r._cleanups.add(()=>g(u)),R(()=>g(u))}else{let i=K(o);if(r.appendChild(i),i._mounts)i._mounts.forEach((l)=>l())}};return c(n),r},k=(e)=>{let t=new Set,n=_,s=d,r=S.createElement("div");r.style.display="contents",r.setAttribute("role","presentation"),_={_cleanups:t},d=null;let c=(o)=>{if(!o)return;if(o._isRuntime)t.add(o.destroy),r.appendChild(o.container);else if(N(o))o.forEach(c);else r.appendChild(o instanceof Node?o:S.createTextNode(String(o==null?"":o)))};try{c(e({onCleanup:(o)=>t.add(o)}))}finally{_=n,d=s}return{_isRuntime:!0,container:r,destroy:()=>{t.forEach((o)=>o()),M(r),r.remove()}}},D=(e,t,n=null)=>{let s=S.createTextNode(""),r=b("div",{style:"display:contents"},[s]),c=null;return v(()=>!!(w(e)?e():e),(o)=>{if(c)c.destroy(),c=null;let i=o?t:n;if(i)c=k(()=>w(i)?i():i),r.insertBefore(c.container,s)}),R(()=>c?.destroy()),r},G=(e,t,n)=>{let s=S.createTextNode(""),r=b("div",{style:"display:contents"},[s]),c=new Map;return v(()=>(w(e)?e():e)||[],(o)=>{let i=new Map,l=[],u=o||[];for(let f=0;ft(h,f));else c.delete(p);i.set(p,y),l.push(y)}c.forEach((f)=>f.destroy());let a=s;for(let f=l.length-1;f>=0;f--){let p=l[f].container;if(p.nextSibling!==a)r.insertBefore(p,a);a=p}c=i}),r},E=(e)=>{let t=()=>window.location.hash.slice(1)||"/",n=T(t()),s=()=>n(t());window.addEventListener("hashchange",s),R(()=>window.removeEventListener("hashchange",s));let r=b("div",{class:"router-hook"}),c=null;return v([n],()=>{let o=n(),i=e.find((l)=>{let u=l.path.split("/").filter(Boolean),a=o.split("/").filter(Boolean);return u.length===a.length&&u.every((f,h)=>f[0]===":"||f===a[h])})||e.find((l)=>l.path==="*");if(i){c?.destroy();let l={};i.path.split("/").filter(Boolean).forEach((u,a)=>{if(u[0]===":")l[u.slice(1)]=o.split("/").filter(Boolean)[a]}),E.params(l),c=k(()=>w(i.component)?i.component(l):i.component),r.replaceChildren(c.container)}}),r};E.params=T({});E.to=(e)=>window.location.hash=e.replace(/^#?\/?/,"#/");E.back=()=>window.history.back();E.path=()=>window.location.hash.replace(/^#/,"")||"/";var W=(e,t)=>{let n=typeof t==="string"?S.querySelector(t):t;if(!n)return;if(U.has(n))U.get(n).destroy();let s=k(w(e)?e:()=>e);return n.replaceChildren(s.container),U.set(n,s),s};if(typeof window<"u")"a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video".split(" ").forEach((e)=>{window[e]=(t,n)=>b(e,t,n)}),console.log("SigPro tags ready");var ie=/^\s*(javascript|data|vbscript):/i,le=new Set(["src","href","formaction","action","background","code","archive"]),ae=(e)=>le.has(e)||e.startsWith("on"),fe=(e,t)=>{if(t==null||t===!1)return null;if(ae(e)){let n=String(t);if(ie.test(n))return console.warn(`[SigPro XSS] Locked ${e}`),"#"}return t};B(fe);if(typeof window<"u"){let e={};for(let t of[["$",T],["$$",j],["watch",v],["h",b],["when",D],["each",G],["router",E],["mount",W],["batch",I]])e[t[0]]={value:t[1],writable:!1,configurable:!1,enumerable:!0};Object.defineProperties(window,e)}})(); diff --git a/docs/README.md b/docs/README.md index 8230a9a..25caf94 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,23 +22,6 @@
  • SigPro: You ship Pure Vanilla JS. The runtime is so small that it often costs less than a single high‑res icon.
  • -

    Two Ways to Use SigPro

    -

    🎯 Modern ESM (recommended): Import only the functions you need – zero global pollution.

    -
    import { $, div, button, mount } from 'sigpro';
    -const count = $(0);
    -mount(() => div([ button({ onclick: () => count(count()+1) }, count) ]), '#app');
    -

    🌍 Classic Global (IIFE): Load the script and everything is automatically on window – no imports needed.

    -
    <script src="https://cdn.jsdelivr.net/npm/sigpro@1.2.19/dist/sigpro.js"></script>
    -<script>
    -const count = $(0);
    -mount(() => div([ button({ onclick: () => count(count()+1) }, count) ]), '#app');
    -</script>
    -

    🔧 ESM + Global injection: If you want the global helpers but still use type="module", call sigpro().

    -
    <script type="module">
    -import { sigpro } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/+esm';
    -sigpro();   // now $, div, button, etc. are global
    -</script>
    -

    Precision Engineering

    1. Functional Efficiency

    div(), button(), span()… These aren't just wrappers; they are pre‑optimized constructors. When you call div({ class: 'btn' }, "Click"), SigPro creates the element and attaches its reactive listeners in a single, surgical operation.

    diff --git a/docs/_sidebar.md b/docs/_sidebar.md index e823f7a..1a6d4ae 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -11,10 +11,10 @@ * [when](api/when.md) * [each](api/each.md) * [router](api/router.md) - * [fx](api/fx.md) - * [req](api/req.md) * [mount](api/mount.md) * [h](api/h.md) + +* **Concepts** * [Tags](api/tags.md) * [Global Store](api/global.md) * [JSX Style](api/jsx.md) \ No newline at end of file diff --git a/docs/api/fx.md b/docs/api/fx.md deleted file mode 100644 index 855a74f..0000000 --- a/docs/api/fx.md +++ /dev/null @@ -1,184 +0,0 @@ -# Animation Helper: `fx( )` - -The `fx` function applies simple **enter animations** to DOM elements. You can either use a predefined CSS keyframes animation or declare inline transition effects (scale, slide, rotate, blur). It is designed to be used when dynamically creating elements – especially inside `when` or `each` branches. - -## Function Signature - -```typescript -fx( - options: { - name?: string; // CSS keyframes animation name (will append '-in') - duration?: number; // Animation duration in ms (default: 200) - scale?: boolean; // Start with scale(0.95) → none - slide?: boolean; // Start with translateY(-10px) → none - rotate?: boolean; // Start with rotate(-2deg) → none - blur?: boolean; // Start with blur(4px) → none - }, - child: Node | (() => Node) -): Node -``` - -| Parameter | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **`options`** | `object` | Yes | Animation configuration. | -| **`options.name`** | `string` | No | Name of a CSS `@keyframes` animation. The actual animation name becomes `${name}-in`. | -| **`options.duration`** | `number` | No | Duration in milliseconds (default `200`). | -| **`options.scale`** | `boolean` | No | Add a scale transform from `0.95` to `none`. | -| **`options.slide`** | `boolean` | No | Add a vertical slide from `translateY(-10px)` to `none`. | -| **`options.rotate`** | `boolean` | No | Add a small rotation from `rotate(-2deg)` to `none`. | -| **`options.blur`** | `boolean` | No | Add a blur filter from `blur(4px)` to `none`. | -| **`child`** | `Node` or `() => Node` | Yes | The element to animate. If a function is passed, it is called to obtain the node. | - -**Returns:** The same DOM node (or the child if it is not a `Node`), after applying the animation setup. - -> **Availability:** `fx` is exported from the SigPro module. In **ESM** you must import it (`import { fx } from 'sigpro'`) or inject all globals via `sigpro()`. In the **IIFE** classic script, it is automatically available on `window`. The examples below assume the function is already in scope. - ---- - -## Usage Patterns - -### 1. Named CSS Keyframes Animation - -Define a `@keyframes` rule in your CSS, for example: - -```css -@keyframes fade-in { - from { opacity: 0; } - to { opacity: 1; } -} -``` - -Then apply it with `fx`: - -```javascript -const MyComponent = () => - fx({ name: "fade", duration: 300 }, - div("I will fade in") - ); -``` - -> The animation name used is `${name}-in`. In this example: `fade-in`. - -### 2. Inline Transition (Scale + Opacity) - -No CSS keyframes needed. The element starts with `opacity: 0` and `transform: scale(0.95)`, then transitions to `opacity: 1` and `transform: none`. - -```javascript -fx({ scale: true, duration: 200 }, - button({ onClick: () => alert("Hi") }, "Click me") -); -``` - -### 3. Combining Multiple Effects - -You can combine `scale`, `slide`, `rotate`, and `blur` at the same time. - -```javascript -fx({ scale: true, slide: true, blur: true, duration: 250 }, - div({ class: "card" }, "Smooth enter") -); -``` - -### 4. Using with `when` (Conditional Rendering) - -Wrap the branch content with `fx` to animate entering elements. - -```javascript -when(show, - () => fx({ slide: true }, - div("This slides in when visible") - ) -); -``` - -### 5. Using a Function as Child - -If the element is created inside a function (e.g. to avoid recreation until needed), pass a function that returns the node. - -```javascript -fx({ scale: true }, - () => div("Lazy created and then animated") -); -``` - ---- - -## What Happens Under the Hood - -### With `name` (CSS animation) - -- Sets `el.style.animation = `${name}-in ${duration}ms``. -- The element animates according to your keyframes. -- No further inline style changes are applied. - -### Without `name` (transition effects) - -- Sets `el.style.transition = `all ${duration}ms ease``. -- Sets initial `opacity: 0`. -- Applies initial transforms (`scale`, `slide`, `rotate`) if selected. -- Applies initial `filter: blur(4px)` if `blur: true`. -- In the next animation frame (via `requestAnimationFrame`), sets: - - `opacity: 1` - - `transform: none` - - `filter: none` -- The element transitions smoothly from the start state to the final state. - -> **Important:** The element must be **in the DOM** when the animation starts. `fx` does **not** automatically mount the node – you must already have appended it or be about to mount it. In practice, when you call `fx` inside a component that is being mounted, the element will be added to the DOM shortly after, and the animation runs correctly. - ---- - -## Complete Example - -```javascript -const App = () => - div([ - fx({ name: "fade", duration: 400 }, - h1("Welcome to SigPro") - ), - fx({ scale: true, slide: true, duration: 250 }, - button({ onClick: () => alert("Animated!") }, "Animated button") - ) - ]); - -mount(App, "#app"); -``` - -With accompanying CSS: - -```css -@keyframes fade-in { - from { opacity: 0; transform: translateY(10px); } - to { opacity: 1; transform: translateY(0); } -} -``` - ---- - -## Notes - -- `fx` is **not** required for basic reactivity – it is purely a visual helper for enter animations. -- For exit animations (when an element is removed), use CSS transitions on the element itself combined with `when` – or consider adding a wrapper that delays removal. SigPro does not include built‑in exit animations. -- The function returns the same node you passed, so you can inline it inside `h` or tag helpers: - -```javascript -div([ - fx({ scale: true }, span("Hello")) -]) -``` - -- If `child` is not a DOM node (e.g., a string or number), `fx` returns it unchanged – no animation is applied. - ---- - -## Summary - -| Option | Effect | -| :--- | :--- | -| `name` | Uses `@keyframes ${name}-in` CSS animation. | -| `duration` | Controls animation/transition length (ms). | -| `scale` | Start scale `0.95` → `none`. | -| `slide` | Start `translateY(-10px)` → `none`. | -| `rotate` | Start `rotate(-2deg)` → `none`. | -| `blur` | Start `blur(4px)` → `none`. | - -Combine options to create smooth, modern entrance effects without writing extra CSS. \ No newline at end of file diff --git a/docs/api/global.md b/docs/api/global.md index c29dcc2..23e4549 100644 --- a/docs/api/global.md +++ b/docs/api/global.md @@ -112,6 +112,7 @@ export const filteredTodos = $(() => { ```javascript // components/TodoApp.js import { todos, filter, addTodo, toggleTodo, filteredTodos } from "../store/todos.js"; +import "sigpro/tags" // tags helpers available in global const TodoApp = () => div({ class: "todo-app" }, [ diff --git a/docs/api/mount.md b/docs/api/mount.md index 0b03f6d..c5572a0 100644 --- a/docs/api/mount.md +++ b/docs/api/mount.md @@ -120,6 +120,7 @@ When `destroy()` is called (or when a new mount replaces an old one), everything ```javascript import { $, mount, div, h1, button } from 'sigpro'; +import "sigpro/tags" // tags helpers available in global const App = () => { const count = $(0); diff --git a/docs/api/quick.md b/docs/api/quick.md index 2fcae39..cd382c8 100644 --- a/docs/api/quick.md +++ b/docs/api/quick.md @@ -1,17 +1,19 @@ -# ⚡ SigPro – Complete API Reference +# SigPro – Complete API Reference SigPro is a **Real‑DOM first** reactive micro‑framework. No virtual DOM, no diffing overhead – it updates the DOM directly with surgical precision. Built‑in automatic cleanup prevents memory leaks, and the API is designed to be both tiny and powerful. ```javascript -import { $, $$, watch, h, when, each, fx, router, req, mount, batch } from 'sigpro' -// Or, if you prefer the global style in an ESM environment: -// import { sigpro } from 'sigpro'; sigpro(); // then $, h, div... become window globals -// In a classic IIFE script (` -- Or in ESM, call `sigpro()` after importing: `import { sigpro } from 'sigpro'; sigpro();` - -After that, you can write `div()`, `span()`, etc. directly, without any import. +In the **IIFE bundle** (`sigpro.min.js`), the helpers are already injected globally – no import needed. Available tags: `a`, `abbr`, `article`, `aside`, `audio`, `b`, `blockquote`, `br`, `button`, `canvas`, `caption`, `cite`, `code`, `col`, `colgroup`, `datalist`, `dd`, `del`, `details`, `dfn`, `dialog`, `div`, `dl`, `dt`, `em`, `embed`, `fieldset`, `figcaption`, `figure`, `footer`, `form`, `h1`…`h6`, `header`, `hr`, `i`, `iframe`, `img`, `input`, `ins`, `kbd`, `label`, `legend`, `li`, `main`, `mark`, `meter`, `nav`, `object`, `ol`, `optgroup`, `option`, `output`, `p`, `picture`, `pre`, `progress`, `section`, `select`, `slot`, `small`, `source`, `span`, `strong`, `sub`, `summary`, `sup`, `svg`, `table`, `tbody`, `td`, `template`, `textarea`, `tfoot`, `th`, `thead`, `time`, `tr`, `u`, `ul`, `video`. --- -## 🧩 Flow Control Components +## Built‑in XSS Shield (Optional) + +SigPro includes an optional security layer that sanitises dangerous attributes (`href`, `src`, `formaction`, etc.) and blocks `javascript:` / `data:` / `vbscript:` protocols. +To enable it in ESM environments: + +```javascript +import 'sigpro/xss' +``` + +In the IIFE bundle, the shield is **active by default**. + +When the shield is enabled, trying to set `href="javascript:alert(1)"` will log a warning and replace the value with `'#'`. + +--- + +## Flow Control Components ### `when(condition, thenComponent, elseComponent?)` @@ -162,7 +176,7 @@ When the array changes, elements are added, removed, or reordered with minimal D --- -## 💥 Batch +## Batch ### `batch(fn)` @@ -178,33 +192,13 @@ batch(() => { --- -## ✨ Animations – `fx(options, child)` - -Applies smooth enter animations (CSS transitions / keyframes). Returns the modified element. - -```javascript -fx({ name: 'fade', duration: 300 }, - div({}, 'Hello') -) -``` - -**Options** -- `name` – uses predefined keyframes `${name}-in` (you must define them in your CSS) -- `duration` – in ms (default 200) -- `scale` – adds `scale(0.95)` → `none` -- `slide` – adds `translateY(-10px)` → `none` -- `rotate` – adds `rotate(-2deg)` → `none` -- `blur` – adds `blur(4px)` → `none` - -If `name` is given, it sets `animation: ${name}-in ${duration}ms`. Otherwise it applies a smooth transition from the initial transform/filter to the final state. - ---- - -## 🧭 Router – `router(routes)` +## Router – `router(routes)` Hash‑based SPA router. Returns a DOM node that renders the current route. ```javascript +import { router } from 'sigpro' + const routes = [ { path: '/', component: () => div({}, 'Home') }, { path: '/user/:id', component: (params) => div({}, `User ${params.id}`) }, @@ -229,44 +223,7 @@ const App = () => div({}, [ --- -## 🌐 HTTP Requests – `req(config)` - -Creates a reactive request controller with built‑in loading/error/data signals and abort support. - -```javascript -const fetchUser = req({ url: '/api/user/1', method: 'GET' }) - -// start the request -fetchUser.run().catch(console.error) - -// reactively display state -watch(() => { - if (fetchUser.loading()) console.log('loading...') - if (fetchUser.error()) console.error(fetchUser.error()) - if (fetchUser.data()) console.log(fetchUser.data()) -}) - -// abort if needed -fetchUser.abort() -``` - -**Options** -- `url` (required) -- `method` (default `'GET'`) -- `headers` (object, default `{}`) - -**Return value** -- `run(body?)` – initiates the request, returns a promise. -- `abort()` – aborts the current request (AbortController). -- `loading` – signal (boolean) -- `error` – signal (`null` or error message) -- `data` – signal (`null` or parsed JSON) - -> **Note**: Automatically sets `Content-Type: application/json` unless `body` is a `FormData`. Timeout after 10 seconds aborts the request. - ---- - -## 🚀 Mounting – `mount(component, target)` +## Mounting – `mount(component, target)` Clears the target element and mounts the application. Returns the runtime instance (which has a `.destroy()` method). @@ -280,7 +237,7 @@ If you mount again on the same target, the previous instance is automatically de --- -## 🧹 Global Cleanup & Memory +## Global Cleanup & Memory SigPro tracks every effect, DOM event listener, and nested component. When a component is unmounted: - All its effects are disposed. @@ -292,10 +249,12 @@ You never need to manually clean up – just write reactive code. --- -## 📦 Full Example – Counter with Persistence +## Full Example – Counter with Persistence ```javascript -import { $, watch, h, mount } from 'sigpro' +import { $, watch, mount } from 'sigpro' +import 'sigpro/tags' // ← activate global helpers +import 'sigpro/xss' // ← activate security (optional) const count = $(0, 'counter') // persists in localStorage @@ -320,8 +279,8 @@ You can rename everything in one line: import { $ as signal, watch as effect, h as element, mount as render } from 'sigpro' ``` -Or assign globally (after calling `sigpro()` or using the classic script): +In the IIFE bundle, the core functions are already available as both `window.$` and `window.SigPro.$`, so you can alias them globally if needed: ```javascript -window.myReactive = $ +window.myReactive = $ // or window.SigPro.$ ``` \ No newline at end of file diff --git a/docs/api/req.md b/docs/api/req.md deleted file mode 100644 index 5acd97c..0000000 --- a/docs/api/req.md +++ /dev/null @@ -1,184 +0,0 @@ -# HTTP Requests: `req( )` - -The `req` function creates a **reactive HTTP request controller**. It returns signals for `loading`, `error`, and `data`, plus a `run` method to execute the request and an `abort` method to cancel it. All signals update automatically as the request progresses. - -## Function Signature - -```typescript -req(config: { - url: string; - method?: string; // default: 'GET' - headers?: Record; -}): { - run: (body?: any) => Promise; - abort: () => void; - loading: Signal; - error: Signal; - data: Signal; -} -``` - -| Parameter | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **`url`** | `string` | Yes | The endpoint to call. | -| **`method`** | `string` | No | HTTP method (`'GET'`, `'POST'`, etc.). Default `'GET'`. | -| **`headers`** | `object` | No | Custom headers (will be merged with defaults). | - -**Returns:** A controller object with reactive signals and methods. - -> **Availability:** `req` is exported from the SigPro module. In **ESM** you must import it (`import { req } from 'sigpro'`) or inject all globals via `sigpro()`. In the **IIFE** classic script, it is automatically available on `window`. The examples below assume the function is already in scope. - ---- - -## Usage Patterns - -### 1. Basic GET Request - -```javascript -const users = req({ url: '/api/users' }); - -// Start the request -users.run().catch(console.error); - -// React to the response -watch(() => { - if (users.loading()) console.log('Loading...'); - if (users.error()) console.error(users.error()); - if (users.data()) console.log('Data:', users.data()); -}); -``` - -### 2. POST Request with Body - -```javascript -const createUser = req({ url: '/api/users', method: 'POST' }); - -const handleSubmit = async (formData) => { - try { - await createUser.run(formData); - alert('User created!'); - } catch (err) { - // Error already in createUser.error() - } -}; -``` - -### 3. Aborting a Request - -```javascript -const search = req({ url: '/api/search' }); - -// Abort if the user types again quickly -let timeout; -input({ onInput: (e) => { - clearTimeout(timeout); - search.abort(); // cancel previous in-flight request - timeout = setTimeout(() => search.run({ q: e.target.value }), 300); -}}); -``` - -### 4. Reactive UI with Signals - -```javascript -const profile = req({ url: '/api/me' }); - -const App = () => - div([ - when(() => profile.loading(), - () => div("Loading...") - ), - when(() => profile.error(), - () => div("Error: " + profile.error()) - ), - when(() => profile.data(), - () => div([ - h2(profile.data().name), - p(profile.data().email) - ]) - ) - ]); - -profile.run(); -mount(App, '#app'); -``` - ---- - -## Request Lifecycle - -When you call `run(body?)`: - -1. Any previous request is **aborted** automatically. -2. `loading` becomes `true`. -3. `error` is cleared. -4. A 10‑second timeout is set (auto‑abort). -5. The request is sent using `fetch`. -6. On success: `data` is set with the parsed JSON, `loading` becomes `false`. -7. On error: `error` is set with the message, `data` is cleared, `loading` becomes `false`. - -> **Important:** The response body is parsed as JSON. If the response is not OK or the JSON parsing fails, `error` is set and the promise rejects. - ---- - -## Automatic Headers - -- If `body` is a plain object or array, the request automatically includes `Content-Type: application/json` (unless you override it in `headers`). -- If `body` is a `FormData` instance, the `Content-Type` is not set (browser will set it automatically with the correct boundary). -- Other headers can be added via the `headers` option. - -```javascript -const upload = req({ - url: '/upload', - method: 'POST', - headers: { 'X-Custom': 'value' } -}); - -const formData = new FormData(); -formData.append('file', fileInput.files[0]); -upload.run(formData); -``` - ---- - -## Error Handling - -- Network errors, timeouts, and HTTP error statuses (4xx, 5xx) all set `error` and cause the promise to reject. -- The `error` signal contains a human‑readable message. -- Abort errors (calling `abort()` or timeout) are **silent** – they do not set `error` or reject the promise? - Actually, according to the source: if `e.name === 'AbortError'`, it does **not** call `error(e.message)`, but the promise still rejects with the AbortError. You can handle it with `.catch()` if needed. - ---- - -## Complete Example - -```javascript -const api = req({ url: 'https://jsonplaceholder.typicode.com/posts/1' }); - -const App = () => - div({ class: "demo" }, [ - when(() => api.loading(), () => p("⏳ Loading...")), - when(() => api.error(), () => p("❌ " + api.error())), - when(() => api.data(), () => div([ - h2(api.data().title), - p(api.data().body) - ])), - button({ - onClick: () => api.run(), - disabled: () => api.loading() - }, "Fetch") - ]); - -mount(App, '#app'); -``` - ---- - -## Summary - -| Member | Type | Description | -| :--- | :--- | :--- | -| `run(body?)` | `(any) => Promise` | Starts the request. Returns a promise. | -| `abort()` | `() => void` | Cancels the current request. | -| `loading` | `Signal` | `true` while request is in flight. | -| `error` | `Signal` | Contains an error message, or `null`. | -| `data` | `Signal` | Contains the parsed response (JSON), or `null`. | \ No newline at end of file diff --git a/docs/api/router.md b/docs/api/router.md index 85158cc..7b8d0df 100644 --- a/docs/api/router.md +++ b/docs/api/router.md @@ -153,6 +153,7 @@ If you want the router outlet to have no layout impact, you can set `display: co ```javascript import { $, router, mount } from "sigpro"; +import "sigpro/tags" // tags helpers available in global const Home = () => div("Welcome home"); const About = () => div("About us"); diff --git a/docs/api/tags.md b/docs/api/tags.md index 0f28904..5e71d05 100644 --- a/docs/api/tags.md +++ b/docs/api/tags.md @@ -4,58 +4,72 @@ In **SigPro**, you don't need to manually type `h('div', ...)` for every element ## 1. How it Works -SigPro iterates through a list of standard HTML tags and creates a wrapper function for each one. +SigPro creates a wrapper function for each standard HTML tag. - **Under the hood:** `h('button', { onclick: ... }, 'Click')` - **SigPro Style:** `button({ onclick: ... }, 'Click')` -> **Note:** All tag helpers are **lowercase** (e.g., `div`, `span`, `button`). This keeps the syntax close to raw HTML. - -These helpers can be used in two ways, depending on your environment: - -### Mode A: Classic (IIFE) – Auto‑global -When you load the **IIFE bundle** (`sigpro.js`) with a traditional ` - -``` - -### Mode B: ESM (Modern) – Explicit or Imported -When you import the **ES module** (via `import` or CDN with `type="module"`), nothing is added to `window` by default. You have two options: - -1. **Manual global injection** – import `sigpro` and call it: - ```javascript - import { sigpro } from 'sigpro'; - sigpro(); // now div, span, button, etc. become global - ``` -2. **Named imports** (recommended) – import the helpers you need directly: - ```javascript - import { div, span, button } from 'sigpro'; - // use them directly - ``` +> **Note:** All tag helpers are **lowercase** (e.g., `div`, `span`, `button`) and can be used directly once globally enabled. --- -## 2. The Complete List of Tag Helpers +## 2. Activating the Tag Helpers -All helpers are **lowercase** and follow HTML5 tag names. You can use them globally (after injection) or import them individually. +Depending on how you load SigPro, the activation varies: + +### A. Classic IIFE – Automatic Global Helpers +When you use the **IIFE bundle** (`sigpro.js` or `sigpro.min.js`) with a traditional ` + +``` + +### B. ESM (Modern JavaScript) – Explicit Activation +When you import the **ES module** (`import { ... } from 'sigpro'`), the core **does not** add helpers to `window` by default. To enable global tags, import the dedicated side‑effect module: + +```js +import 'sigpro/tags'; // ← activates window.div, window.span, etc. + +// Now you can use helpers globally +const App = () => div({ class: "app" }, "Ready!"); +``` + +If you also want built‑in **XSS protection**, enable it once: + +```js +import 'sigpro/xss'; // ← add security layer +import 'sigpro/tags'; // ← global helpers +``` + +Both are side‑effect modules, so the order doesn’t matter. + +> **Important:** The tag helpers are **not** exported as individual named exports from the core (`sigpro`). They become available as global functions (`window.div`, etc.) after the side‑effect runs. +> If you prefer to avoid globals, you can always use `h('div', ...)` directly—it’s perfectly fine. + +--- + +## 3. The Complete List of Tag Helpers + +All helpers are **lowercase** and follow HTML5 tag names. | Category | Available functions | | :--- | :--- | | **Structure** | `div`, `span`, `p`, `section`, `nav`, `main`, `header`, `footer`, `article`, `aside` | -| **Typography** | `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `ul`, `ol`, `li`, `dl`, `dt`, `dd`, `strong`, `em`, `code`, `pre`, `small`, `b`, `u`, `mark` | +| **Typography** | `h1`…`h6`, `ul`, `ol`, `li`, `dl`, `dt`, `dd`, `strong`, `em`, `code`, `pre`, `small`, `b`, `u`, `mark` | | **Interactive** | `button`, `a`, `label`, `br`, `hr`, `details`, `summary`, `dialog` | | **Forms** | `form`, `input`, `select`, `option`, `textarea`, `fieldset`, `legend` | | **Tables** | `table`, `thead`, `tbody`, `tr`, `th`, `td`, `tfoot`, `caption` | | **Media** | `img`, `canvas`, `video`, `audio`, `svg`, `iframe`, `picture`, `source` | -Full list includes all standard tags: `a`, `abbr`, `article`, `aside`, `audio`, `b`, `blockquote`, `br`, `button`, `canvas`, `caption`, `cite`, `code`, `col`, `colgroup`, `datalist`, `dd`, `del`, `details`, `dfn`, `dialog`, `div`, `dl`, `dt`, `em`, `embed`, `fieldset`, `figcaption`, `figure`, `footer`, `form`, `h1`…`h6`, `header`, `hr`, `i`, `iframe`, `img`, `input`, `ins`, `kbd`, `label`, `legend`, `li`, `main`, `mark`, `meter`, `nav`, `object`, `ol`, `optgroup`, `option`, `output`, `p`, `picture`, `pre`, `progress`, `section`, `select`, `slot`, `small`, `source`, `span`, `strong`, `sub`, `summary`, `sup`, `svg`, `table`, `tbody`, `td`, `template`, `textarea`, `tfoot`, `th`, `thead`, `time`, `tr`, `u`, `ul`, `video`. +Full list: `a`, `abbr`, `article`, `aside`, `audio`, `b`, `blockquote`, `br`, `button`, `canvas`, `caption`, `cite`, `code`, `col`, `colgroup`, `datalist`, `dd`, `del`, `details`, `dfn`, `dialog`, `div`, `dl`, `dt`, `em`, `embed`, `fieldset`, `figcaption`, `figure`, `footer`, `form`, `h1`…`h6`, `header`, `hr`, `i`, `iframe`, `img`, `input`, `ins`, `kbd`, `label`, `legend`, `li`, `main`, `mark`, `meter`, `nav`, `object`, `ol`, `optgroup`, `option`, `output`, `p`, `picture`, `pre`, `progress`, `section`, `select`, `slot`, `small`, `source`, `span`, `strong`, `sub`, `summary`, `sup`, `svg`, `table`, `tbody`, `td`, `template`, `textarea`, `tfoot`, `th`, `thead`, `time`, `tr`, `u`, `ul`, `video`. --- -## 3. Usage Patterns +## 4. Usage Patterns ### A. Attributes + Children @@ -79,7 +93,7 @@ section([ --- -## 4. Reactive Power +## 5. Reactive Power These helpers are natively wired into SigPro's reactivity system. @@ -126,7 +140,7 @@ div([ --- -## 5. Custom Components with `h()` or Tag Helpers +## 6. Custom Components with `h()` or Tag Helpers While the tag helpers cover all standard HTML tags, you can create reusable components using them directly. @@ -170,7 +184,7 @@ const Timer = () => { --- -## 6. Comparison with `h()` +## 7. Comparison with `h()` | Use case | Recommendation | | :--- | :--- | @@ -182,11 +196,17 @@ const Timer = () => { --- -## 7. Complete Example +## 8. Complete Example + +### ESM (modern projects) ```javascript -// In a modern ESM environment (recommended) -import { div, h1, input, p, button, mount, $ } from 'sigpro'; +// Enable global helpers + security +import 'sigpro/tags'; +import 'sigpro/xss'; + +// Import core functions +import { $, mount } from 'sigpro'; const nameSignal = $(''); @@ -204,10 +224,10 @@ const App = () => mount(App, '#app'); ``` -Or using the classic script (auto‑global): +### Classic IIFE (auto‑global) ```html - + ``` @@ -66,10 +67,10 @@ bun add sigpro
    ```html - - + + + +