lego-dom 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,22 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to this project will be documented in this file.
3
+ ## [1.5.0] - 2026-01-19
4
+
5
+ ### Breaking Changes 🚨
6
+
7
+ - **Renamed `b-styles` to `b-stylesheets`:** The directive for injecting shared stylesheets into shadow roots has been renamed from `b-styles` to `b-stylesheets` to avoid confusion with the `style` attribute and better reflect its purpose (injecting Constructable Stylesheets).
8
+ ```html
9
+ <!-- Before -->
10
+ <template b-styles="base theme">
11
+
12
+ <!-- After -->
13
+ <template b-stylesheets="base theme">
14
+ ```
15
+
16
+ ### Documentation
17
+
18
+ - **New "Helper Utilities" Guide:** Added a dedicated page at `/docs/guide/helpers.md` documenting core instance methods like `$ancestors`, `$emit`, `$route`, `$go`, `$vars`, `$element`, and `$registry`.
19
+ - **Performance Tips Updated:** Revised the "Performance Tips" section in the Directives guide to correctly compare `b-show` (toggle visibility) vs `b-if` (DOM insertion/removal) instead of the outdated `b-show` vs CSS comparison.
4
20
 
5
21
  ## [1.4.0] - 2026-01-16
6
22
 
package/main.js CHANGED
@@ -101,6 +101,24 @@ const Lego = (() => {
101
101
  const componentsToUpdate = new Set();
102
102
  let isProcessing = false;
103
103
 
104
+ // Optimization: Single timer for all updated hooks instead of one per component
105
+ let updatedTimer = null;
106
+ const pendingUpdated = new Set();
107
+
108
+ const scheduleUpdatedHooks = () => {
109
+ if (updatedTimer) clearTimeout(updatedTimer);
110
+ updatedTimer = setTimeout(() => {
111
+ pendingUpdated.forEach(el => {
112
+ const state = el._studs;
113
+ if (state && typeof state.updated === 'function') {
114
+ try { state.updated.call(state); } catch (e) { console.error(`[Lego] Error in updated hook:`, e); }
115
+ }
116
+ });
117
+ pendingUpdated.clear();
118
+ updatedTimer = null;
119
+ }, 50); // Global debounce
120
+ };
121
+
104
122
  return {
105
123
  add: (el) => {
106
124
  if (!el || isProcessing) return;
@@ -114,21 +132,13 @@ const Lego = (() => {
114
132
  componentsToUpdate.clear();
115
133
  queued = false;
116
134
 
135
+ // Simple synchronous render loop (faster for small batches, no overhead)
117
136
  batch.forEach(el => render(el));
118
137
 
119
- setTimeout(() => {
120
- batch.forEach(el => {
121
- const state = el._studs;
122
- if (state && typeof state.updated === 'function') {
123
- try {
124
- state.updated.call(state);
125
- } catch (e) {
126
- console.error(`[Lego] Error in updated hook:`, e);
127
- }
128
- }
129
- });
130
- isProcessing = false;
131
- }, 0);
138
+ // Batch complete: Queue updated hooks efficiently
139
+ batch.forEach(el => pendingUpdated.add(el));
140
+ scheduleUpdatedHooks();
141
+ isProcessing = false;
132
142
  });
133
143
  }
134
144
  };
@@ -540,7 +550,7 @@ const Lego = (() => {
540
550
  const tpl = templateNode.content.cloneNode(true);
541
551
  const shadow = el.attachShadow({ mode: 'open' });
542
552
 
543
- const styleKeys = (templateNode.getAttribute('b-styles') || "").split(/\s+/).filter(Boolean);
553
+ const styleKeys = (templateNode.getAttribute('b-stylesheets') || "").split(/\s+/).filter(Boolean);
544
554
  if (styleKeys.length > 0) {
545
555
  const sheetsToApply = styleKeys.flatMap(key => styleRegistry.get(key) || []);
546
556
  if (sheetsToApply.length > 0) {
package/main.min.js CHANGED
@@ -1,7 +1,7 @@
1
- const Lego=(()=>{const w={},M=new WeakMap,k=new WeakMap,O=new WeakMap,$=new Set,P=new Map,B=new Map,H=new Map,q=new Map;let G={};const p={onError:(e,s,c)=>{console.error(`[Lego Error] [${s}]`,e,c)},metrics:{},syntax:"brackets"},_=()=>p.syntax==="brackets"?["[[","]]"]:["{{","}}"],L=()=>{const[e,s]=_(),c=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),o=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`${c}(.*?)${o}`,"g")},R=[],ee=e=>typeof e!="string"?e:e.replace(/[&<>"']/g,s=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[s]),K=e=>{const c=e.split("/").pop().replace(/\.lego$/,"").replace(/_/g,"-").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase();if(!c.includes("-"))throw new Error(`[Lego] Invalid component definition: "${e}". Component names must contain a hyphen (e.g. user-card.lego or UserCard.lego).`);return c},V=(e,s)=>{if(typeof e=="function"){const o=Array.from(document.querySelectorAll("*")).filter(t=>t.tagName.includes("-"));return[].concat(e(o))}if(e.startsWith("#")){const o=document.getElementById(e.slice(1));return o?[o]:[]}const c=s?.querySelectorAll(e)||[];return c.length>0?[...c]:[...document.querySelectorAll(e)]},j=(e,...s)=>c=>{const o=async(t,a=null,r=!0,n={})=>{if(r){const d={legoTargets:s.filter(i=>typeof i=="string"),method:t,body:a};history.pushState(d,"",e)}await F(s.length?s:null,c)};return{get:(t=!0,a={})=>o("GET",null,t,a),post:(t,a=!0,r={})=>o("POST",t,a,r),put:(t,a=!0,r={})=>o("PUT",t,a,r),patch:(t,a=!0,r={})=>o("PATCH",t,a,r),delete:(t=!0,a={})=>o("DELETE",null,t,a)}},J=(()=>{let e=!1;const s=new Set;let c=!1;return{add:o=>{!o||c||(s.add(o),!e&&(e=!0,requestAnimationFrame(()=>{c=!0;const t=Array.from(s);s.clear(),e=!1,t.forEach(a=>C(a)),setTimeout(()=>{t.forEach(a=>{const r=a._studs;if(r&&typeof r.updated=="function")try{r.updated.call(r)}catch(n){console.error("[Lego] Error in updated hook:",n)}}),c=!1},0)})))}}})(),S=(e,s,c=J)=>{if(e===null||typeof e!="object"||e instanceof Node)return e;if(M.has(e))return M.get(e);const o={get:(a,r)=>{const n=Reflect.get(a,r);return n!==null&&typeof n=="object"&&!(n instanceof Node)?S(n,s,c):n},set:(a,r,n)=>{const l=a[r],d=Reflect.set(a,r,n);return l!==n&&c.add(s),d},deleteProperty:(a,r)=>{const n=Reflect.deleteProperty(a,r);return c.add(s),n}},t=new Proxy(e,o);return M.set(e,t),t},I=e=>{try{return new Function(`return (${e})`)()}catch(s){return console.error("[Lego] Failed to parse b-data:",e,s),{}}},N=e=>(k.has(e)||k.set(e,{snapped:!1,bindings:null,bound:!1,rendering:!1,anchor:null,hasGlobalDependency:!1}),k.get(e)),D=(e,s)=>{if(!e)return"";const c=e.trim().split(".");let o=s;for(const t of c){if(o==null)return"";o=o[t]}return o??""},Z=(e,s)=>{let c=e.parentElement||e.getRootNode().host;for(;c;){if(c.tagName&&c.tagName.toLowerCase()===s.toLowerCase())return c._studs;c=c.parentElement||c.getRootNode&&c.getRootNode().host}},h=(e,s,c=!1)=>{if(/\b(function|eval|import|class|module|deploy|constructor|__proto__)\b/.test(e)){console.warn(`[Lego] Security Warning: Blocked dangerous expression "${e}"`);return}try{const o=s.state||{};let t=H.get(e);t||(t=new Function("global","self","event","helpers",`
1
+ const Lego=(()=>{const b={},M=new WeakMap,O=new WeakMap,P=new WeakMap,v=new Set,D=new Map,B=new Map,H=new Map,j=new Map;let q={};const p={onError:(e,s,c)=>{console.error(`[Lego Error] [${s}]`,e,c)},metrics:{},syntax:"brackets"},$=()=>p.syntax==="brackets"?["[[","]]"]:["{{","}}"],L=()=>{const[e,s]=$(),c=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),o=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`${c}(.*?)${o}`,"g")},R=[],ee=e=>typeof e!="string"?e:e.replace(/[&<>"']/g,s=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[s]),K=e=>{const c=e.split("/").pop().replace(/\.lego$/,"").replace(/_/g,"-").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase();if(!c.includes("-"))throw new Error(`[Lego] Invalid component definition: "${e}". Component names must contain a hyphen (e.g. user-card.lego or UserCard.lego).`);return c},V=(e,s)=>{if(typeof e=="function"){const o=Array.from(document.querySelectorAll("*")).filter(t=>t.tagName.includes("-"));return[].concat(e(o))}if(e.startsWith("#")){const o=document.getElementById(e.slice(1));return o?[o]:[]}const c=s?.querySelectorAll(e)||[];return c.length>0?[...c]:[...document.querySelectorAll(e)]},G=(e,...s)=>c=>{const o=async(t,a=null,r=!0,n={})=>{if(r){const d={legoTargets:s.filter(i=>typeof i=="string"),method:t,body:a};history.pushState(d,"",e)}await W(s.length?s:null,c)};return{get:(t=!0,a={})=>o("GET",null,t,a),post:(t,a=!0,r={})=>o("POST",t,a,r),put:(t,a=!0,r={})=>o("PUT",t,a,r),patch:(t,a=!0,r={})=>o("PATCH",t,a,r),delete:(t=!0,a={})=>o("DELETE",null,t,a)}},J=(()=>{let e=!1;const s=new Set;let c=!1,o=null;const t=new Set,a=()=>{o&&clearTimeout(o),o=setTimeout(()=>{t.forEach(r=>{const n=r._studs;if(n&&typeof n.updated=="function")try{n.updated.call(n)}catch(l){console.error("[Lego] Error in updated hook:",l)}}),t.clear(),o=null},50)};return{add:r=>{!r||c||(s.add(r),!e&&(e=!0,requestAnimationFrame(()=>{c=!0;const n=Array.from(s);s.clear(),e=!1,n.forEach(l=>x(l)),n.forEach(l=>t.add(l)),a(),c=!1})))}}})(),S=(e,s,c=J)=>{if(e===null||typeof e!="object"||e instanceof Node)return e;if(M.has(e))return M.get(e);const o={get:(a,r)=>{const n=Reflect.get(a,r);return n!==null&&typeof n=="object"&&!(n instanceof Node)?S(n,s,c):n},set:(a,r,n)=>{const l=a[r],d=Reflect.set(a,r,n);return l!==n&&c.add(s),d},deleteProperty:(a,r)=>{const n=Reflect.deleteProperty(a,r);return c.add(s),n}},t=new Proxy(e,o);return M.set(e,t),t},U=e=>{try{return new Function(`return (${e})`)()}catch(s){return console.error("[Lego] Failed to parse b-data:",e,s),{}}},N=e=>(O.has(e)||O.set(e,{snapped:!1,bindings:null,bound:!1,rendering:!1,anchor:null,hasGlobalDependency:!1}),O.get(e)),F=(e,s)=>{if(!e)return"";const c=e.trim().split(".");let o=s;for(const t of c){if(o==null)return"";o=o[t]}return o??""},Z=(e,s)=>{let c=e.parentElement||e.getRootNode().host;for(;c;){if(c.tagName&&c.tagName.toLowerCase()===s.toLowerCase())return c;c=c.parentElement||c.getRootNode&&c.getRootNode().host}},h=(e,s,c=!1)=>{if(/\b(function|eval|import|class|module|deploy|constructor|__proto__)\b/.test(e)){console.warn(`[Lego] Security Warning: Blocked dangerous expression "${e}"`);return}try{const o=s.state||{};let t=H.get(e);t||(t=new Function("global","self","event","helpers",`
2
2
  with(helpers) {
3
3
  with(this) {
4
4
  return ${e}
5
5
  }
6
6
  }
7
- `),H.set(e,t));const a={$ancestors:n=>Z(s.self,n),$registry:n=>B.get(n.toLowerCase()),$element:s.self,$route:Lego.globals.$route,$go:(n,...l)=>j(n,...l)(s.self),$emit:(n,l)=>{s.self.dispatchEvent(new CustomEvent(n,{detail:l,bubbles:!0,composed:!0}))}},r=t.call(o,s.global,s.self,s.event,a);return typeof r=="function"?r.call(o,s.event):r}catch(o){if(c)throw o;p.onError(o,"render-error",s.self);return}},z=(e,s)=>{if(e.type==="checkbox")e.checked!==!!s&&(e.checked=!!s);else{const c=s==null?"":String(s);e.value!==c&&(e.value=c)}},x=(e,s,c=null)=>{const o=s._studs,t=n=>{const l=N(n);if(!l.bound){if(n.hasAttributes()){const d=n.attributes;for(let i=0;i<d.length;i++){const u=d[i];if(u.name.startsWith("@")){const f=u.name.slice(1);n.addEventListener(f,g=>{try{let m=o;if(c){const y=h(c.listName,{state:o,global:Lego.globals,self:s})[c.index];m=Object.assign(Object.create(o),{[c.name]:y})}h(u.value,{state:m,global:Lego.globals,self:n,event:g},!0)}catch(m){p.onError(m,"event-handler",n)}})}}if(n.hasAttribute("b-sync")){const i=n.getAttribute("b-sync"),u=()=>{try{let f,g;if(c&&i.startsWith(c.name+".")){const y=h(c.listName,{state:o,global:Lego.globals,self:s})[c.index];if(!y)return;const E=i.split(".").slice(1);g=E.pop(),f=E.reduce((T,A)=>T[A],y)}else{const b=i.split(".");g=b.pop(),f=b.reduce((y,E)=>y[E],o)}const m=n.type==="checkbox"?n.checked:n.value;f&&f[g]!==m&&(f[g]=m)}catch(f){p.onError(f,"sync-update",n)}};n.addEventListener("input",u),n.addEventListener("change",u)}if(n.hasAttribute("b-var")){const i=n.getAttribute("b-var");o.$vars&&(o.$vars[i]=n)}}l.bound=!0}};e instanceof Element&&t(e);const a=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT);let r;for(;r=a.nextNode();)t(r)},Y=e=>{const s=[],c=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT);let o;for(;o=c.nextNode();){if((r=>{let n=r.parentNode;for(;n&&n!==e;){if(n.hasAttribute&&n.hasAttribute("b-for"))return!0;n=n.parentNode}return!1})(o))continue;const a=r=>{if(/\bglobal\b/.test(r)){const n=e.host||e;N(n).hasGlobalDependency=!0}};if(o.nodeType===Node.ELEMENT_NODE){if(o.hasAttribute("b-if")){const n=o.getAttribute("b-if");a(n);const l=document.createComment(`b-if: ${n}`),d=N(o);d.anchor=l,s.push({type:"b-if",node:o,anchor:l,expr:n})}if(o.hasAttribute("b-show")){const n=o.getAttribute("b-show");a(n),s.push({type:"b-show",node:o,expr:n})}if(o.hasAttribute("b-for")){const n=o.getAttribute("b-for").match(/^\s*(\w+)\s+in\s+([\s\S]+?)\s*$/);n&&(a(n[2]),s.push({type:"b-for",node:o,itemName:n[1],listName:n[2].trim(),template:o.cloneNode(!0)}),o.innerHTML="")}if(o.hasAttribute("b-text")&&s.push({type:"b-text",node:o,path:o.getAttribute("b-text")}),o.hasAttribute("b-html")){const n=o.getAttribute("b-html");a(n),s.push({type:"b-html",node:o,expr:n})}o.hasAttribute("b-sync")&&s.push({type:"b-sync",node:o});const[r]=_();[...o.attributes].forEach(n=>{n.value.includes(r)&&(a(n.value),s.push({type:"attr",node:o,attrName:n.name,template:n.value}))})}else if(o.nodeType===Node.TEXT_NODE){const[r]=_();o.textContent.includes(r)&&(a(o.textContent),s.push({type:"text",node:o,template:o.textContent}))}}return s},Q=(e,s)=>{const c=a=>{if(a.nodeType===Node.TEXT_NODE){a._tpl===void 0&&(a._tpl=a.textContent);const r=a._tpl.replace(L(),(n,l)=>h(l.trim(),{state:s,global:Lego.globals,self:a})??"");a.textContent!==r&&(a.textContent=r)}else if(a.nodeType===Node.ELEMENT_NODE){const[r]=_();[...a.attributes].forEach(n=>{if(n._tpl===void 0&&(n._tpl=n.value),n._tpl.includes(r)){const l=n._tpl.replace(L(),(d,i)=>h(i.trim(),{state:s,global:Lego.globals,self:a})??"");n.value!==l&&(n.value=l,n.name==="class"&&(a.className=l))}})}};c(e);const o=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT);let t;for(;t=o.nextNode();)c(t)},C=e=>{const s=e._studs;if(!s)return;const c=N(e);if(!c.rendering){c.rendering=!0,p.metrics&&p.metrics.onRenderStart&&p.metrics.onRenderStart(e);try{const o=e.shadowRoot||e;c.bindings||(c.bindings=Y(o)),c.bindings.forEach(t=>{if(t.type==="b-if"){const a=!!h(t.expr,{state:s,global:Lego.globals,self:t.node}),r=!!t.node.parentNode;a&&!r?t.anchor.parentNode&&t.anchor.parentNode.replaceChild(t.node,t.anchor):!a&&r&&t.node.parentNode.replaceChild(t.anchor,t.node)}if(t.type==="b-show"&&(t.node.style.display=h(t.expr,{state:s,global:Lego.globals,self:t.node})?"":"none"),t.type==="b-text"&&(t.node.textContent=D(t.path,s)),t.type==="b-html"&&(t.node.innerHTML=h(t.expr,{state:s,global:Lego.globals,self:t.node})||""),t.type==="b-sync"&&z(t.node,D(t.node.getAttribute("b-sync"),s)),t.type==="text"){const a=t.template.replace(L(),(r,n)=>h(n.trim(),{state:s,global:Lego.globals,self:t.node})??"");t.node.textContent!==a&&(t.node.textContent=a)}if(t.type==="attr"){const a=t.template.replace(L(),(r,n)=>h(n.trim(),{state:s,global:Lego.globals,self:t.node})??"");t.node.getAttribute(t.attrName)!==a&&(t.node.setAttribute(t.attrName,a),t.attrName==="class"&&(t.node.className=a))}if(t.type==="b-for"){const a=h(t.listName,{state:s,global:Lego.globals,self:e})||[];O.has(t.node)||O.set(t.node,new Map);const r=O.get(t.node),n=new Set;a.forEach((l,d)=>{const i=l&&typeof l=="object"?l.__id||(l.__id=Math.random()):`${d}-${l}`;n.add(i);let u=r.get(i);u||(u=t.template.cloneNode(!0),u.removeAttribute("b-for"),r.set(i,u),x(u,e,{name:t.itemName,listName:t.listName,index:d}));const f=Object.assign(Object.create(s),{[t.itemName]:l});Q(u,f),u.querySelectorAll("[b-sync]").forEach(g=>{const m=g.getAttribute("b-sync");if(m.startsWith(t.itemName+".")){const b=h(t.listName,{state:s,global:Lego.globals,self:e});z(g,D(m.split(".").slice(1).join("."),b[d]))}}),t.node.children[d]!==u&&t.node.insertBefore(u,t.node.children[d]||null)});for(const[l,d]of r.entries())n.has(l)||(d.remove(),r.delete(l))}}),s===Lego.globals&&$.forEach(t=>{N(t).hasGlobalDependency&&C(t)})}catch(o){p.onError(o,"render",e)}finally{p.metrics&&p.metrics.onRenderEnd&&p.metrics.onRenderEnd(e),c.rendering=!1}}},v=e=>{if(!e||e.nodeType!==Node.ELEMENT_NODE)return;const s=N(e),c=e.tagName.toLowerCase(),o=w[c];if(o&&!s.snapped){s.snapped=!0;const a=o.content.cloneNode(!0),r=e.attachShadow({mode:"open"}),n=(o.getAttribute("b-styles")||"").split(/\s+/).filter(Boolean);if(n.length>0){const f=n.flatMap(g=>q.get(g)||[]);f.length>0&&(r.adoptedStyleSheets=[...f])}const l=P.get(c)||{},d=I(o.getAttribute("b-data")||"{}"),i=I(e.getAttribute("b-data")||"{}");e._studs=S({...l,...d,...i,$vars:{},$element:e,$emit:(f,g)=>{e.dispatchEvent(new CustomEvent(f,{detail:g,bubbles:!0,composed:!0}))},get $route(){return Lego.globals.$route},get $go(){return Lego.globals.$go}},e),r.appendChild(a);const u=r.querySelector("style");if(u&&(u.textContent=u.textContent.replace(/\bself\b/g,":host")),x(r,e),$.add(e),C(e),[...r.children].forEach(v),typeof e._studs.mounted=="function")try{e._studs.mounted.call(e._studs)}catch(f){p.onError(f,"mounted",e)}}let t=e.parentElement;for(;t&&!t._studs;)t=t.parentElement;t&&t._studs&&x(e,t),[...e.children].forEach(v)},W=e=>{if(e._studs&&typeof e._studs.unmounted=="function")try{e._studs.unmounted.call(e._studs)}catch(s){console.error("[Lego] Error in unmounted:",s)}e.shadowRoot&&[...e.shadowRoot.children].forEach(W),$.delete(e),[...e.children].forEach(W)},F=async(e=null,s=null)=>{const c=window.location.pathname,o=window.location.search,t=R.find(d=>d.regex.test(c));if(!t)return;let a=[];if(e)a=e.flatMap(d=>V(d,s));else{const d=document.querySelector("lego-router");d&&(a=[d])}if(a.length===0)return;const r=c.match(t.regex).slice(1),n=Object.fromEntries(t.paramNames.map((d,i)=>[d,r[i]])),l=Object.fromEntries(new URLSearchParams(o));t.middleware&&!await t.middleware(n,Lego.globals)||(Lego.globals.$route.url=c+o,Lego.globals.$route.route=t.path,Lego.globals.$route.params=n,Lego.globals.$route.query=l,Lego.globals.$route.method=history.state?.method||"GET",Lego.globals.$route.body=history.state?.body||null,a.forEach(d=>{if(d){const i=document.createElement(t.tagName);d.replaceChildren(i)}}))},U={init:async(e=document.body,s={})=>{(!e||typeof e.nodeType!="number")&&(e=document.body),G=s.styles||{},p.loader=s.loader;const c=Object.entries(G).map(async([t,a])=>{const r=await Promise.all(a.map(async n=>{try{const d=await(await fetch(n)).text(),i=new CSSStyleSheet;return await i.replace(d),i}catch(l){return console.error(`[Lego] Failed to load stylesheet: ${n}`,l),null}}));q.set(t,r.filter(n=>n!==null))});await Promise.all(c),document.querySelectorAll("template[b-id]").forEach(t=>{w[t.getAttribute("b-id")]=t}),new MutationObserver(t=>t.forEach(a=>{a.addedNodes.forEach(r=>{if(r.nodeType===Node.ELEMENT_NODE){v(r);const n=r.tagName.toLowerCase();if(n.includes("-")&&!w[n]&&p.loader&&!$.has(r)){const l=p.loader(n);if(l){const d=typeof l=="string"?fetch(l).then(i=>i.text()):l;Promise.resolve(d).then(i=>U.defineSFC(i,n+".lego")).catch(i=>console.error(`[Lego] Failed to load ${n}:`,i))}}}}),a.removedNodes.forEach(r=>r.nodeType===Node.ELEMENT_NODE&&W(r))})).observe(e,{childList:!0,subtree:!0}),e._studs=Lego.globals,v(e),x(e,e),C(e),R.length>0&&(window.addEventListener("popstate",t=>{const a=t.state?.legoTargets||null;F(a)}),document.addEventListener("submit",t=>{t.preventDefault()}),document.addEventListener("click",t=>{const r=t.composedPath().find(n=>n.tagName==="A"&&(n.hasAttribute("b-target")||n.hasAttribute("b-link")));if(r){t.preventDefault();const n=r.getAttribute("href"),l=r.getAttribute("b-target"),d=l?l.split(/\s+/).filter(Boolean):[],i=r.getAttribute("b-link")!=="false";Lego.globals.$go(n,...d).get(i)}}),F())},globals:S({$route:{url:window.location.pathname,route:"",params:{},query:{},method:"GET",body:null},$go:(e,...s)=>j(e,...s)(document.body)},document.body),defineSFC:(e,s="component.lego")=>{let c="",o="{}",t="",a="",r=e;const n=/<(template|script|style)\b((?:\s+(?:[^>"']|"[^"]*"|'[^']*')*)*)>/i;for(;r;){const i=r.match(n);if(!i)break;const u=i[1].toLowerCase(),f=i[2],g=i[0],m=i.index,b=`</${u}>`,y=m+g.length,E=r.indexOf(b,y);if(E===-1){console.warn(`[Lego] Unclosed <${u}> tag in ${s}`);break}const T=r.slice(y,E);if(u==="template"){c=T.trim();const A=f.match(/b-styles=["']([^"']+)["']/);A&&(t=A[1])}else if(u==="script"){const A=T.trim(),X=A.match(/export\s+default\s+({[\s\S]*})/);o=X?X[1]:A}else u==="style"&&(a=T.trim());r=r.slice(E+b.length)}const l=K(s),d=new Function(`return ${o}`)();a&&(c=`<style>${a}</style>`+c),w[l]=document.createElement("template"),w[l].innerHTML=c,w[l].setAttribute("b-styles",t),P.set(l,d),document.querySelectorAll(l).forEach(i=>!N(i).snapped&&v(i))},define:(e,s,c={},o="")=>{const t=document.createElement("template");t.setAttribute("b-id",e),t.setAttribute("b-styles",o),t.innerHTML=s,w[e]=t,P.set(e,c);try{B.set(e.toLowerCase(),S({...c},document.body))}catch(a){p.onError(a,"define",e)}document.querySelectorAll(e).forEach(v)},getActiveComponentsCount:()=>$.size,config:p,route:(e,s,c=null)=>{const o=[],t=e.replace(/:([^\/]+)/g,(a,r)=>(o.push(r),"([^/]+)"));R.push({path:e,regex:new RegExp(`^${t}$`),tagName:s,paramNames:o,middleware:c})}};return U})();typeof window<"u"&&(window.Lego=Lego);
7
+ `),H.set(e,t));const a={$ancestors:n=>Z(s.self,n),$registry:n=>B.get(n.toLowerCase()),$element:s.self,$route:Lego.globals.$route,$go:(n,...l)=>G(n,...l)(s.self),$emit:(n,l)=>{s.self.dispatchEvent(new CustomEvent(n,{detail:l,bubbles:!0,composed:!0}))}},r=t.call(o,s.global,s.self,s.event,a);return typeof r=="function"?r.call(o,s.event):r}catch(o){if(c)throw o;p.onError(o,"render-error",s.self);return}},I=(e,s)=>{if(e.type==="checkbox")e.checked!==!!s&&(e.checked=!!s);else{const c=s==null?"":String(s);e.value!==c&&(e.value=c)}},C=(e,s,c=null)=>{const o=s._studs,t=n=>{const l=N(n);if(!l.bound){if(n.hasAttributes()){const d=n.attributes;for(let i=0;i<d.length;i++){const u=d[i];if(u.name.startsWith("@")){const f=u.name.slice(1);n.addEventListener(f,g=>{try{let m=o;if(c){const E=h(c.listName,{state:o,global:Lego.globals,self:s})[c.index];m=Object.assign(Object.create(o),{[c.name]:E})}h(u.value,{state:m,global:Lego.globals,self:n,event:g},!0)}catch(m){p.onError(m,"event-handler",n)}})}}if(n.hasAttribute("b-sync")){const i=n.getAttribute("b-sync"),u=()=>{try{let f,g;if(c&&i.startsWith(c.name+".")){const E=h(c.listName,{state:o,global:Lego.globals,self:s})[c.index];if(!E)return;const w=i.split(".").slice(1);g=w.pop(),f=w.reduce((T,_)=>T[_],E)}else{const y=i.split(".");g=y.pop(),f=y.reduce((E,w)=>E[w],o)}const m=n.type==="checkbox"?n.checked:n.value;f&&f[g]!==m&&(f[g]=m)}catch(f){p.onError(f,"sync-update",n)}};n.addEventListener("input",u),n.addEventListener("change",u)}if(n.hasAttribute("b-var")){const i=n.getAttribute("b-var");o.$vars&&(o.$vars[i]=n)}}l.bound=!0}};e instanceof Element&&t(e);const a=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT);let r;for(;r=a.nextNode();)t(r)},Y=e=>{const s=[],c=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT);let o;for(;o=c.nextNode();){if((r=>{let n=r.parentNode;for(;n&&n!==e;){if(n.hasAttribute&&n.hasAttribute("b-for"))return!0;n=n.parentNode}return!1})(o))continue;const a=r=>{if(/\bglobal\b/.test(r)){const n=e.host||e;N(n).hasGlobalDependency=!0}};if(o.nodeType===Node.ELEMENT_NODE){if(o.hasAttribute("b-if")){const n=o.getAttribute("b-if");a(n);const l=document.createComment(`b-if: ${n}`),d=N(o);d.anchor=l,s.push({type:"b-if",node:o,anchor:l,expr:n})}if(o.hasAttribute("b-show")){const n=o.getAttribute("b-show");a(n),s.push({type:"b-show",node:o,expr:n})}if(o.hasAttribute("b-for")){const n=o.getAttribute("b-for").match(/^\s*(\w+)\s+in\s+([\s\S]+?)\s*$/);n&&(a(n[2]),s.push({type:"b-for",node:o,itemName:n[1],listName:n[2].trim(),template:o.cloneNode(!0)}),o.innerHTML="")}if(o.hasAttribute("b-text")&&s.push({type:"b-text",node:o,path:o.getAttribute("b-text")}),o.hasAttribute("b-html")){const n=o.getAttribute("b-html");a(n),s.push({type:"b-html",node:o,expr:n})}o.hasAttribute("b-sync")&&s.push({type:"b-sync",node:o});const[r]=$();[...o.attributes].forEach(n=>{n.value.includes(r)&&(a(n.value),s.push({type:"attr",node:o,attrName:n.name,template:n.value}))})}else if(o.nodeType===Node.TEXT_NODE){const[r]=$();o.textContent.includes(r)&&(a(o.textContent),s.push({type:"text",node:o,template:o.textContent}))}}return s},Q=(e,s)=>{const c=a=>{if(a.nodeType===Node.TEXT_NODE){a._tpl===void 0&&(a._tpl=a.textContent);const r=a._tpl.replace(L(),(n,l)=>h(l.trim(),{state:s,global:Lego.globals,self:a})??"");a.textContent!==r&&(a.textContent=r)}else if(a.nodeType===Node.ELEMENT_NODE){const[r]=$();[...a.attributes].forEach(n=>{if(n._tpl===void 0&&(n._tpl=n.value),n._tpl.includes(r)){const l=n._tpl.replace(L(),(d,i)=>h(i.trim(),{state:s,global:Lego.globals,self:a})??"");n.value!==l&&(n.value=l,n.name==="class"&&(a.className=l))}})}};c(e);const o=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT);let t;for(;t=o.nextNode();)c(t)},x=e=>{const s=e._studs;if(!s)return;const c=N(e);if(!c.rendering){c.rendering=!0,p.metrics&&p.metrics.onRenderStart&&p.metrics.onRenderStart(e);try{const o=e.shadowRoot||e;c.bindings||(c.bindings=Y(o)),c.bindings.forEach(t=>{if(t.type==="b-if"){const a=!!h(t.expr,{state:s,global:Lego.globals,self:t.node}),r=!!t.node.parentNode;a&&!r?t.anchor.parentNode&&t.anchor.parentNode.replaceChild(t.node,t.anchor):!a&&r&&t.node.parentNode.replaceChild(t.anchor,t.node)}if(t.type==="b-show"&&(t.node.style.display=h(t.expr,{state:s,global:Lego.globals,self:t.node})?"":"none"),t.type==="b-text"&&(t.node.textContent=F(t.path,s)),t.type==="b-html"&&(t.node.innerHTML=h(t.expr,{state:s,global:Lego.globals,self:t.node})||""),t.type==="b-sync"&&I(t.node,F(t.node.getAttribute("b-sync"),s)),t.type==="text"){const a=t.template.replace(L(),(r,n)=>h(n.trim(),{state:s,global:Lego.globals,self:t.node})??"");t.node.textContent!==a&&(t.node.textContent=a)}if(t.type==="attr"){const a=t.template.replace(L(),(r,n)=>h(n.trim(),{state:s,global:Lego.globals,self:t.node})??"");t.node.getAttribute(t.attrName)!==a&&(t.node.setAttribute(t.attrName,a),t.attrName==="class"&&(t.node.className=a))}if(t.type==="b-for"){const a=h(t.listName,{state:s,global:Lego.globals,self:e})||[];P.has(t.node)||P.set(t.node,new Map);const r=P.get(t.node),n=new Set;a.forEach((l,d)=>{const i=l&&typeof l=="object"?l.__id||(l.__id=Math.random()):`${d}-${l}`;n.add(i);let u=r.get(i);u||(u=t.template.cloneNode(!0),u.removeAttribute("b-for"),r.set(i,u),C(u,e,{name:t.itemName,listName:t.listName,index:d}));const f=Object.assign(Object.create(s),{[t.itemName]:l});Q(u,f),u.querySelectorAll("[b-sync]").forEach(g=>{const m=g.getAttribute("b-sync");if(m.startsWith(t.itemName+".")){const y=h(t.listName,{state:s,global:Lego.globals,self:e});I(g,F(m.split(".").slice(1).join("."),y[d]))}}),t.node.children[d]!==u&&t.node.insertBefore(u,t.node.children[d]||null)});for(const[l,d]of r.entries())n.has(l)||(d.remove(),r.delete(l))}}),s===Lego.globals&&v.forEach(t=>{N(t).hasGlobalDependency&&x(t)})}catch(o){p.onError(o,"render",e)}finally{p.metrics&&p.metrics.onRenderEnd&&p.metrics.onRenderEnd(e),c.rendering=!1}}},A=e=>{if(!e||e.nodeType!==Node.ELEMENT_NODE)return;const s=N(e),c=e.tagName.toLowerCase(),o=b[c];if(o&&!s.snapped){s.snapped=!0;const a=o.content.cloneNode(!0),r=e.attachShadow({mode:"open"}),n=(o.getAttribute("b-stylesheets")||"").split(/\s+/).filter(Boolean);if(n.length>0){const f=n.flatMap(g=>j.get(g)||[]);f.length>0&&(r.adoptedStyleSheets=[...f])}const l=D.get(c)||{},d=U(o.getAttribute("b-data")||"{}"),i=U(e.getAttribute("b-data")||"{}");e._studs=S({...l,...d,...i,$vars:{},$element:e,$emit:(f,g)=>{e.dispatchEvent(new CustomEvent(f,{detail:g,bubbles:!0,composed:!0}))},get $route(){return Lego.globals.$route},get $go(){return Lego.globals.$go}},e),Object.defineProperty(e,"state",{get(){return this._studs},set(f){Object.assign(this._studs,f)},configurable:!0,enumerable:!1}),r.appendChild(a);const u=r.querySelector("style");if(u&&(u.textContent=u.textContent.replace(/\bself\b/g,":host")),C(r,e),v.add(e),x(e),[...r.children].forEach(A),typeof e._studs.mounted=="function")try{e._studs.mounted.call(e._studs)}catch(f){p.onError(f,"mounted",e)}}let t=e.parentElement;for(;t&&!t._studs;)t=t.parentElement;t&&t._studs&&C(e,t),[...e.children].forEach(A)},k=e=>{if(e._studs&&typeof e._studs.unmounted=="function")try{e._studs.unmounted.call(e._studs)}catch(s){console.error("[Lego] Error in unmounted:",s)}e.shadowRoot&&[...e.shadowRoot.children].forEach(k),v.delete(e),[...e.children].forEach(k)},W=async(e=null,s=null)=>{const c=window.location.pathname,o=window.location.search,t=R.find(d=>d.regex.test(c));if(!t)return;let a=[];if(e)a=e.flatMap(d=>V(d,s));else{const d=document.querySelector("lego-router");d&&(a=[d])}if(a.length===0)return;const r=c.match(t.regex).slice(1),n=Object.fromEntries(t.paramNames.map((d,i)=>[d,r[i]])),l=Object.fromEntries(new URLSearchParams(o));t.middleware&&!await t.middleware(n,Lego.globals)||(Lego.globals.$route.url=c+o,Lego.globals.$route.route=t.path,Lego.globals.$route.params=n,Lego.globals.$route.query=l,Lego.globals.$route.method=history.state?.method||"GET",Lego.globals.$route.body=history.state?.body||null,a.forEach(d=>{if(d){const i=document.createElement(t.tagName);d.replaceChildren(i)}}))},z={snap:A,unsnap:k,init:async(e=document.body,s={})=>{(!e||typeof e.nodeType!="number")&&(e=document.body),q=s.styles||{},p.loader=s.loader;const c=Object.entries(q).map(async([t,a])=>{const r=await Promise.all(a.map(async n=>{try{const d=await(await fetch(n)).text(),i=new CSSStyleSheet;return await i.replace(d),i}catch(l){return console.error(`[Lego] Failed to load stylesheet: ${n}`,l),null}}));j.set(t,r.filter(n=>n!==null))});if(await Promise.all(c),document.querySelectorAll("template[b-id]").forEach(t=>{b[t.getAttribute("b-id")]=t}),new MutationObserver(t=>t.forEach(a=>{a.addedNodes.forEach(r=>{if(r.nodeType===Node.ELEMENT_NODE){A(r);const n=r.tagName.toLowerCase();if(n.includes("-")&&!b[n]&&p.loader&&!v.has(r)){const l=p.loader(n);if(l){const d=typeof l=="string"?fetch(l).then(i=>i.text()):l;Promise.resolve(d).then(i=>z.defineSFC(i,n+".lego")).catch(i=>console.error(`[Lego] Failed to load ${n}:`,i))}}}}),a.removedNodes.forEach(r=>r.nodeType===Node.ELEMENT_NODE&&k(r))})).observe(e,{childList:!0,subtree:!0}),e._studs=Lego.globals,A(e),C(e,e),x(e),s.studio){if(!b["lego-studio"]){const t=document.createElement("script");t.src="https://unpkg.com/@legodom/studio@0.0.2/dist/lego-studio.js",t.onerror=()=>console.warn("[Lego] Failed to load Studio from CDN"),document.head.appendChild(t)}Lego.route("/_/studio","lego-studio"),Lego.route("/_/studio/:component","lego-studio")}R.length>0&&(window.addEventListener("popstate",t=>{const a=t.state?.legoTargets||null;W(a)}),document.addEventListener("submit",t=>{t.preventDefault()}),document.addEventListener("click",t=>{const r=t.composedPath().find(n=>n.tagName==="A"&&(n.hasAttribute("b-target")||n.hasAttribute("b-link")));if(r){t.preventDefault();const n=r.getAttribute("href"),l=r.getAttribute("b-target"),d=l?l.split(/\s+/).filter(Boolean):[],i=r.getAttribute("b-link")!=="false";Lego.globals.$go(n,...d).get(i)}}),W())},globals:S({$route:{url:window.location.pathname,route:"",params:{},query:{},method:"GET",body:null},$go:(e,...s)=>G(e,...s)(document.body)},document.body),defineSFC:(e,s="component.lego")=>{let c="",o="{}",t="",a="",r=e;const n=/<(template|script|style)\b((?:\s+(?:[^>"']|"[^"]*"|'[^']*')*)*)>/i;for(;r;){const i=r.match(n);if(!i)break;const u=i[1].toLowerCase(),f=i[2],g=i[0],m=i.index,y=`</${u}>`,E=m+g.length,w=r.indexOf(y,E);if(w===-1){console.warn(`[Lego] Unclosed <${u}> tag in ${s}`);break}const T=r.slice(E,w);if(u==="template"){c=T.trim();const _=f.match(/b-styles=["']([^"']+)["']/);_&&(t=_[1])}else if(u==="script"){const _=T.trim(),X=_.match(/export\s+default\s+({[\s\S]*})/);o=X?X[1]:_}else u==="style"&&(a=T.trim());r=r.slice(w+y.length)}const l=K(s),d=new Function(`return ${o}`)();a&&(c=`<style>${a}</style>`+c),b[l]=document.createElement("template"),b[l].innerHTML=c,b[l].setAttribute("b-styles",t),D.set(l,d),document.querySelectorAll(l).forEach(i=>!N(i).snapped&&A(i))},define:(e,s,c={},o="")=>{const t=document.createElement("template");t.setAttribute("b-id",e),t.setAttribute("b-styles",o),t.innerHTML=s,b[e]=t,D.set(e,c);try{B.set(e.toLowerCase(),S({...c},document.body))}catch(a){p.onError(a,"define",e)}document.querySelectorAll(e).forEach(A)},getActiveComponentsCount:()=>v.size,getLegos:()=>Object.keys(b),config:p,route:(e,s,c=null)=>{const o=[],t=e.replace(/:([^\/]+)/g,(a,r)=>(o.push(r),"([^/]+)"));R.push({path:e,regex:new RegExp(`^${t}$`),tagName:s,paramNames:o,middleware:c})}};return z})();typeof window<"u"&&(window.Lego=Lego);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lego-dom",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "license": "MIT",
5
5
  "description": "A feature-rich web components + SFC frontend framework",
6
6
  "main": "main.js",
package/parse-lego.js CHANGED
@@ -56,8 +56,8 @@ export function parseLego(content, filename = 'component.lego') {
56
56
 
57
57
  if (tagName === 'template') {
58
58
  result.template = innerContent.trim();
59
- // Extract b-styles attribute if present
60
- const bStylesMatch = attrs.match(/b-styles=["']([^"']+)["']/);
59
+ // Extract b-stylesheets attribute if present
60
+ const bStylesMatch = attrs.match(/b-stylesheets=["']([^"']+)["']/);
61
61
  if (bStylesMatch) {
62
62
  result.stylesAttr = bStylesMatch[1];
63
63
  }