diff --git a/dist/sigpro.js b/dist/sigpro.js index 7f7f2a0..5ab2a29 100644 --- a/dist/sigpro.js +++ b/dist/sigpro.js @@ -317,40 +317,42 @@ }; $if.not = (condition, thenVal, otherwiseVal) => $if(() => !(typeof condition === "function" ? condition() : condition), thenVal, otherwiseVal); var $for = (source, render, keyFn) => { - const marker = document.createTextNode(""); - const container = $html("div", { style: "display:contents" }, [marker]); + const marker = document.createComment("sigpro-for"); let cache = new Map; $watch(() => { const items = (typeof source === "function" ? source() : source) || []; const newCache = new Map; - const newOrder = []; - for (let i = 0;i < items.length; i++) { - const item = items[i]; + const parent = marker.parentNode; + if (!parent) + return; + const newOrder = items.map((item, i) => { const key = keyFn ? keyFn(item, i) : i; - let run = cache.get(key); - if (!run) { - run = _view(() => render(item, i)); + let cached = cache.get(key); + if (!cached) { + const view = _view(() => render(item, i)); + const node = view.container.firstChild || document.createTextNode(""); + cached = { node, destroy: view.destroy }; } else { cache.delete(key); } - newCache.set(key, run); - newOrder.push(key); - } - cache.forEach((run) => { - run.destroy(); - run.container.remove(); + newCache.set(key, cached); + return cached; + }); + cache.forEach((c) => { + c.destroy(); + c.node.remove(); }); let anchor = marker; for (let i = newOrder.length - 1;i >= 0; i--) { - const run = newCache.get(newOrder[i]); - if (run.container.nextSibling !== anchor) { - container.insertBefore(run.container, anchor); + const { node } = newOrder[i]; + if (node.nextSibling !== anchor) { + parent.insertBefore(node, anchor); } - anchor = run.container; + anchor = node; } cache = newCache; }); - return container; + return marker; }; var $router = (routes) => { const sPath = $(window.location.hash.replace(/^#/, "") || "/"); diff --git a/dist/sigpro.min.js b/dist/sigpro.min.js index 044c32b..94335c4 100644 --- a/dist/sigpro.min.js +++ b/dist/sigpro.min.js @@ -1 +1 @@ -(()=>{var{defineProperty:D,getOwnPropertyNames:$,getOwnPropertyDescriptor:w}=Object,k=Object.prototype.hasOwnProperty;var N=new WeakMap,y=(K)=>{var J=N.get(K),Z;if(J)return J;if(J=D({},"__esModule",{value:!0}),K&&typeof K==="object"||typeof K==="function")$(K).map((q)=>!k.call(J,q)&&D(J,q,{get:()=>K[q],enumerable:!(Z=w(K,q))||Z.enumerable}));return N.set(K,J),J};var g=(K,J)=>{for(var Z in J)D(K,Z,{get:J[Z],enumerable:!0,configurable:!0,set:(q)=>J[Z]=()=>q})};var m={};g(m,{$watch:()=>L,$router:()=>Q,$mount:()=>E,$if:()=>T,$html:()=>x,$for:()=>O,$:()=>_});var U=null,I=null,P=new Set,C=!1,M=new WeakMap,v=()=>{if(C)return;C=!0;while(P.size>0){let K=Array.from(P).sort((J,Z)=>(J.depth||0)-(Z.depth||0));P.clear();for(let J of K)if(!J._deleted)J()}C=!1},b=(K)=>{if(U&&!U._deleted)K.add(U),U._deps.add(K)},S=(K)=>{for(let J of K){if(J===U||J._deleted)continue;if(J._isComputed){if(J.markDirty(),J._subs)S(J._subs)}else P.add(J)}if(!C)queueMicrotask(v)},V=(K)=>{if(K._cleanups)K._cleanups.forEach((J)=>J()),K._cleanups.clear();K.childNodes?.forEach(V)},F=(K)=>{let J=new Set,Z=I,q=document.createElement("div");q.style.display="contents",I={cleanups:J};try{let W=K({onCleanup:(j)=>J.add(j)}),Y=(j)=>{if(!j)return;if(j._isRuntime)J.add(j.destroy),q.appendChild(j.container);else if(Array.isArray(j))j.forEach(Y);else q.appendChild(j instanceof Node?j:document.createTextNode(String(j)))};Y(W)}finally{I=Z}return{_isRuntime:!0,container:q,destroy:()=>{J.forEach((W)=>W()),V(q),q.remove()}}},_=(K,J=null)=>{if(typeof K==="function"){let W=new Set,Y,j=!0,X=()=>{if(X._deleted)return;X._deps.forEach((G)=>G.delete(X)),X._deps.clear();let z=U;U=X;try{let G=K();if(!Object.is(Y,G)||j)Y=G,j=!1,S(W)}finally{U=z}};if(X._deps=new Set,X._isComputed=!0,X._subs=W,X._deleted=!1,X.markDirty=()=>j=!0,X.stop=()=>{X._deleted=!0,X._deps.forEach((z)=>z.delete(X)),W.clear()},I)I.cleanups.add(X.stop);return()=>{if(j)X();return b(W),Y}}let Z=K;if(J)try{let W=localStorage.getItem(J);if(W!==null)Z=JSON.parse(W)}catch(W){console.warn("SigPro: LocalStorage locked",W)}let q=new Set;return(...W)=>{if(W.length){let Y=typeof W[0]==="function"?W[0](Z):W[0];if(!Object.is(Z,Y)){if(Z=Y,J)localStorage.setItem(J,JSON.stringify(Z));S(q)}}return b(q),Z}},L=(K,J)=>{let Z=Array.isArray(K),q=Z?J:K,W=Z?K:null;if(typeof q!=="function")return()=>{};let Y=I,j=()=>{if(j._deleted)return;j._deps.forEach((G)=>G.delete(j)),j._deps.clear(),j._cleanups.forEach((G)=>G()),j._cleanups.clear();let X=U,z=I;U=j,I={cleanups:j._cleanups},j.depth=X?X.depth+1:0;try{if(Z)U=null,q(),U=j,W.forEach((G)=>typeof G==="function"&&G());else q()}finally{U=X,I=z}};if(j._deps=new Set,j._cleanups=new Set,j._deleted=!1,j.stop=()=>{if(j._deleted)return;if(j._deleted=!0,P.delete(j),j._deps.forEach((X)=>X.delete(j)),j._cleanups.forEach((X)=>X()),Y)Y.cleanups.delete(j.stop)},Y)Y.cleanups.add(j.stop);return j(),j.stop},x=(K,J={},Z=[])=>{if(J instanceof Node||Array.isArray(J)||typeof J!=="object")Z=J,J={};let q=document.createElement(K),W=(j,X)=>(j==="src"||j==="href")&&String(X).toLowerCase().includes("javascript:")?"#":X;q._cleanups=new Set;for(let[j,X]of Object.entries(J)){if(j==="ref"){typeof X==="function"?X(q):X.current=q;continue}let z=typeof X==="function";if(["INPUT","TEXTAREA","SELECT"].includes(q.tagName)&&(j==="value"||j==="checked")&&z){q._cleanups.add(L(()=>{let H=X();if(q[j]!==H)q[j]=H}));let B=j==="checked"?"change":"input",A=(H)=>X(H.target[j]);q.addEventListener(B,A),q._cleanups.add(()=>q.removeEventListener(B,A))}else if(j.startsWith("on")){let B=j.slice(2).toLowerCase().split(".")[0],A=(H)=>X(H);q.addEventListener(B,A),q._cleanups.add(()=>q.removeEventListener(B,A))}else if(z)q._cleanups.add(L(()=>{let B=W(j,X());if(j==="class")q.className=B||"";else B==null?q.removeAttribute(j):q.setAttribute(j,B)}));else q.setAttribute(j,W(j,X))}let Y=(j)=>{if(Array.isArray(j))return j.forEach(Y);if(typeof j==="function"){let X=document.createTextNode("");q.appendChild(X);let z=[];q._cleanups.add(L(()=>{let G=j(),R=(Array.isArray(G)?G:[G]).map((B)=>B?._isRuntime?B.container:B instanceof Node?B:document.createTextNode(B??""));z.forEach((B)=>{V(B),B.remove()}),R.forEach((B)=>X.parentNode?.insertBefore(B,X)),z=R}))}else q.appendChild(j instanceof Node?j:document.createTextNode(j??""))};return Y(Z),q},T=(K,J,Z=null)=>{let q=document.createTextNode(""),W=x("div",{style:"display:contents"},[q]),Y=null,j=null;return L(()=>{let X=!!(typeof K==="function"?K():K);if(X!==j){if(j=X,Y)Y.destroy();let z=X?J:Z;if(z)Y=F(()=>typeof z==="function"?z():z),W.insertBefore(Y.container,q)}}),W};T.not=(K,J,Z)=>T(()=>!(typeof K==="function"?K():K),J,Z);var O=(K,J,Z)=>{let q=document.createTextNode(""),W=x("div",{style:"display:contents"},[q]),Y=new Map;return L(()=>{let j=(typeof K==="function"?K():K)||[],X=new Map,z=[];for(let R=0;RJ(B,R));else Y.delete(A);X.set(A,H),z.push(A)}Y.forEach((R)=>{R.destroy(),R.container.remove()});let G=q;for(let R=z.length-1;R>=0;R--){let B=X.get(z[R]);if(B.container.nextSibling!==G)W.insertBefore(B.container,G);G=B.container}Y=X}),W},Q=(K)=>{let J=_(window.location.hash.replace(/^#/,"")||"/");window.addEventListener("hashchange",()=>J(window.location.hash.replace(/^#/,"")||"/"));let Z=x("div",{class:"router-outlet"}),q=null;return L([J],async()=>{let W=J(),Y=K.find((j)=>{let X=j.path.split("/").filter(Boolean),z=W.split("/").filter(Boolean);return X.length===z.length&&X.every((G,R)=>G.startsWith(":")||G===z[R])})||K.find((j)=>j.path==="*");if(Y){let j=Y.component;if(typeof j==="function"&&j.toString().includes("import"))j=(await j()).default||await j();let X={};if(Y.path.split("/").filter(Boolean).forEach((z,G)=>{if(z.startsWith(":"))X[z.slice(1)]=W.split("/").filter(Boolean)[G]}),q)q.destroy();if(Q.params)Q.params(X);q=F(()=>{try{return typeof j==="function"?j(X):j}catch(z){return x("div",{class:"p-4 text-error"},"Error loading view")}}),Z.appendChild(q.container)}}),Z};Q.params=_({});Q.to=(K)=>window.location.hash=K.replace(/^#?\/?/,"#/");Q.back=()=>window.history.back();Q.path=()=>window.location.hash.replace(/^#/,"")||"/";var E=(K,J)=>{let Z=typeof J==="string"?document.querySelector(J):J;if(!Z)return;if(M.has(Z))M.get(Z).destroy();let q=F(typeof K==="function"?K:()=>K);return Z.replaceChildren(q.container),M.set(Z,q),q},h={$:_,$watch:L,$html:x,$if:T,$for:O,$router:Q,$mount:E};if(typeof window<"u")((J)=>{Object.keys(J).forEach((q)=>{window[q]=J[q]}),"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((q)=>{let W=q.charAt(0).toUpperCase()+q.slice(1);if(!(W in window))window[W]=(Y,j)=>x(q,Y,j)}),window.SigPro=Object.freeze(J)})(h);})(); +(()=>{var{defineProperty:D,getOwnPropertyNames:w,getOwnPropertyDescriptor:k}=Object,y=Object.prototype.hasOwnProperty;var b=new WeakMap,g=(K)=>{var J=b.get(K),Z;if(J)return J;if(J=D({},"__esModule",{value:!0}),K&&typeof K==="object"||typeof K==="function")w(K).map((q)=>!y.call(J,q)&&D(J,q,{get:()=>K[q],enumerable:!(Z=k(K,q))||Z.enumerable}));return b.set(K,J),J};var v=(K,J)=>{for(var Z in J)D(K,Z,{get:J[Z],enumerable:!0,configurable:!0,set:(q)=>J[Z]=()=>q})};var d={};v(d,{$watch:()=>L,$router:()=>Q,$mount:()=>$,$if:()=>T,$html:()=>x,$for:()=>E,$:()=>_});var U=null,I=null,C=new Set,P=!1,M=new WeakMap,h=()=>{if(P)return;P=!0;while(C.size>0){let K=Array.from(C).sort((J,Z)=>(J.depth||0)-(Z.depth||0));C.clear();for(let J of K)if(!J._deleted)J()}P=!1},O=(K)=>{if(U&&!U._deleted)K.add(U),U._deps.add(K)},S=(K)=>{for(let J of K){if(J===U||J._deleted)continue;if(J._isComputed){if(J.markDirty(),J._subs)S(J._subs)}else C.add(J)}if(!P)queueMicrotask(h)},V=(K)=>{if(K._cleanups)K._cleanups.forEach((J)=>J()),K._cleanups.clear();K.childNodes?.forEach(V)},F=(K)=>{let J=new Set,Z=I,q=document.createElement("div");q.style.display="contents",I={cleanups:J};try{let W=K({onCleanup:(j)=>J.add(j)}),Y=(j)=>{if(!j)return;if(j._isRuntime)J.add(j.destroy),q.appendChild(j.container);else if(Array.isArray(j))j.forEach(Y);else q.appendChild(j instanceof Node?j:document.createTextNode(String(j)))};Y(W)}finally{I=Z}return{_isRuntime:!0,container:q,destroy:()=>{J.forEach((W)=>W()),V(q),q.remove()}}},_=(K,J=null)=>{if(typeof K==="function"){let W=new Set,Y,j=!0,X=()=>{if(X._deleted)return;X._deps.forEach((G)=>G.delete(X)),X._deps.clear();let B=U;U=X;try{let G=K();if(!Object.is(Y,G)||j)Y=G,j=!1,S(W)}finally{U=B}};if(X._deps=new Set,X._isComputed=!0,X._subs=W,X._deleted=!1,X.markDirty=()=>j=!0,X.stop=()=>{X._deleted=!0,X._deps.forEach((B)=>B.delete(X)),W.clear()},I)I.cleanups.add(X.stop);return()=>{if(j)X();return O(W),Y}}let Z=K;if(J)try{let W=localStorage.getItem(J);if(W!==null)Z=JSON.parse(W)}catch(W){console.warn("SigPro: LocalStorage locked",W)}let q=new Set;return(...W)=>{if(W.length){let Y=typeof W[0]==="function"?W[0](Z):W[0];if(!Object.is(Z,Y)){if(Z=Y,J)localStorage.setItem(J,JSON.stringify(Z));S(q)}}return O(q),Z}},L=(K,J)=>{let Z=Array.isArray(K),q=Z?J:K,W=Z?K:null;if(typeof q!=="function")return()=>{};let Y=I,j=()=>{if(j._deleted)return;j._deps.forEach((G)=>G.delete(j)),j._deps.clear(),j._cleanups.forEach((G)=>G()),j._cleanups.clear();let X=U,B=I;U=j,I={cleanups:j._cleanups},j.depth=X?X.depth+1:0;try{if(Z)U=null,q(),U=j,W.forEach((G)=>typeof G==="function"&&G());else q()}finally{U=X,I=B}};if(j._deps=new Set,j._cleanups=new Set,j._deleted=!1,j.stop=()=>{if(j._deleted)return;if(j._deleted=!0,C.delete(j),j._deps.forEach((X)=>X.delete(j)),j._cleanups.forEach((X)=>X()),Y)Y.cleanups.delete(j.stop)},Y)Y.cleanups.add(j.stop);return j(),j.stop},x=(K,J={},Z=[])=>{if(J instanceof Node||Array.isArray(J)||typeof J!=="object")Z=J,J={};let q=document.createElement(K),W=(j,X)=>(j==="src"||j==="href")&&String(X).toLowerCase().includes("javascript:")?"#":X;q._cleanups=new Set;for(let[j,X]of Object.entries(J)){if(j==="ref"){typeof X==="function"?X(q):X.current=q;continue}let B=typeof X==="function";if(["INPUT","TEXTAREA","SELECT"].includes(q.tagName)&&(j==="value"||j==="checked")&&B){q._cleanups.add(L(()=>{let A=X();if(q[j]!==A)q[j]=A}));let z=j==="checked"?"change":"input",H=(A)=>X(A.target[j]);q.addEventListener(z,H),q._cleanups.add(()=>q.removeEventListener(z,H))}else if(j.startsWith("on")){let z=j.slice(2).toLowerCase().split(".")[0],H=(A)=>X(A);q.addEventListener(z,H),q._cleanups.add(()=>q.removeEventListener(z,H))}else if(B)q._cleanups.add(L(()=>{let z=W(j,X());if(j==="class")q.className=z||"";else z==null?q.removeAttribute(j):q.setAttribute(j,z)}));else q.setAttribute(j,W(j,X))}let Y=(j)=>{if(Array.isArray(j))return j.forEach(Y);if(typeof j==="function"){let X=document.createTextNode("");q.appendChild(X);let B=[];q._cleanups.add(L(()=>{let G=j(),R=(Array.isArray(G)?G:[G]).map((z)=>z?._isRuntime?z.container:z instanceof Node?z:document.createTextNode(z??""));B.forEach((z)=>{V(z),z.remove()}),R.forEach((z)=>X.parentNode?.insertBefore(z,X)),B=R}))}else q.appendChild(j instanceof Node?j:document.createTextNode(j??""))};return Y(Z),q},T=(K,J,Z=null)=>{let q=document.createTextNode(""),W=x("div",{style:"display:contents"},[q]),Y=null,j=null;return L(()=>{let X=!!(typeof K==="function"?K():K);if(X!==j){if(j=X,Y)Y.destroy();let B=X?J:Z;if(B)Y=F(()=>typeof B==="function"?B():B),W.insertBefore(Y.container,q)}}),W};T.not=(K,J,Z)=>T(()=>!(typeof K==="function"?K():K),J,Z);var E=(K,J,Z)=>{let q=document.createComment("sigpro-for"),W=new Map;return L(()=>{let Y=(typeof K==="function"?K():K)||[],j=new Map,X=q.parentNode;if(!X)return;let B=Y.map((R,z)=>{let H=Z?Z(R,z):z,A=W.get(H);if(!A){let N=F(()=>J(R,z));A={node:N.container.firstChild||document.createTextNode(""),destroy:N.destroy}}else W.delete(H);return j.set(H,A),A});W.forEach((R)=>{R.destroy(),R.node.remove()});let G=q;for(let R=B.length-1;R>=0;R--){let{node:z}=B[R];if(z.nextSibling!==G)X.insertBefore(z,G);G=z}W=j}),q},Q=(K)=>{let J=_(window.location.hash.replace(/^#/,"")||"/");window.addEventListener("hashchange",()=>J(window.location.hash.replace(/^#/,"")||"/"));let Z=x("div",{class:"router-outlet"}),q=null;return L([J],async()=>{let W=J(),Y=K.find((j)=>{let X=j.path.split("/").filter(Boolean),B=W.split("/").filter(Boolean);return X.length===B.length&&X.every((G,R)=>G.startsWith(":")||G===B[R])})||K.find((j)=>j.path==="*");if(Y){let j=Y.component;if(typeof j==="function"&&j.toString().includes("import"))j=(await j()).default||await j();let X={};if(Y.path.split("/").filter(Boolean).forEach((B,G)=>{if(B.startsWith(":"))X[B.slice(1)]=W.split("/").filter(Boolean)[G]}),q)q.destroy();if(Q.params)Q.params(X);q=F(()=>{try{return typeof j==="function"?j(X):j}catch(B){return x("div",{class:"p-4 text-error"},"Error loading view")}}),Z.appendChild(q.container)}}),Z};Q.params=_({});Q.to=(K)=>window.location.hash=K.replace(/^#?\/?/,"#/");Q.back=()=>window.history.back();Q.path=()=>window.location.hash.replace(/^#/,"")||"/";var $=(K,J)=>{let Z=typeof J==="string"?document.querySelector(J):J;if(!Z)return;if(M.has(Z))M.get(Z).destroy();let q=F(typeof K==="function"?K:()=>K);return Z.replaceChildren(q.container),M.set(Z,q),q},m={$:_,$watch:L,$html:x,$if:T,$for:E,$router:Q,$mount:$};if(typeof window<"u")((J)=>{Object.keys(J).forEach((q)=>{window[q]=J[q]}),"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((q)=>{let W=q.charAt(0).toUpperCase()+q.slice(1);if(!(W in window))window[W]=(Y,j)=>x(q,Y,j)}),window.SigPro=Object.freeze(J)})(m);})(); diff --git a/package.json b/package.json index 98d6685..b9676a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sigpro", - "version": "1.1.18", + "version": "1.1.19", "type": "module", "license": "MIT", "main": "./index.js", diff --git a/sigpro/index.js b/sigpro/index.js index fb6f106..a21c2c3 100644 --- a/sigpro/index.js +++ b/sigpro/index.js @@ -306,50 +306,50 @@ $if.not = (condition, thenVal, otherwiseVal) => $if(() => !(typeof condition === * @param {Function} keyFn - Function to extract a unique key from the item. * @returns {HTMLElement} A reactive container (display: contents). */ - const $for = (source, render, keyFn) => { - const marker = document.createTextNode(""); - const container = $html("div", { style: "display:contents" }, [marker]); + const marker = document.createComment("sigpro-for"); let cache = new Map(); $watch(() => { const items = (typeof source === "function" ? source() : source) || []; const newCache = new Map(); - const newOrder = []; + const parent = marker.parentNode; + + if (!parent) return; - for (let i = 0; i < items.length; i++) { - const item = items[i]; + const newOrder = items.map((item, i) => { const key = keyFn ? keyFn(item, i) : i; - - let run = cache.get(key); - if (!run) { - run = _view(() => render(item, i)); + let cached = cache.get(key); + + if (!cached) { + const view = _view(() => render(item, i)); + const node = view.container.firstChild || document.createTextNode(""); + cached = { node, destroy: view.destroy }; } else { cache.delete(key); } + newCache.set(key, cached); + return cached; + }); - newCache.set(key, run); - newOrder.push(key); - } - - cache.forEach(run => { - run.destroy(); - run.container.remove(); + cache.forEach(c => { + c.destroy(); + c.node.remove(); }); let anchor = marker; for (let i = newOrder.length - 1; i >= 0; i--) { - const run = newCache.get(newOrder[i]); - if (run.container.nextSibling !== anchor) { - container.insertBefore(run.container, anchor); + const { node } = newOrder[i]; + if (node.nextSibling !== anchor) { + parent.insertBefore(node, anchor); } - anchor = run.container; + anchor = node; } cache = newCache; }); - return container; + return marker; }; /**