lego-dom 2.0.4-alpha → 2.0.4-beta

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,15 @@
1
1
  # Changelog
2
2
 
3
3
  # Changelog
4
+ ## [2.0.4-beta] - 2026-01-20
5
+
6
+ ### Fixes
7
+
8
+ - **Auto-Discovery Logic:** Fixed a bug where elements present in the initial HTML were not detected by the `config.loader`. The discovery logic now scans the DOM during `Lego.init()` as well as on subsequent mutations.
9
+ - **b-stylesheets Support:** Fully implemented `b-stylesheets` to allow blocks to adopt constructable stylesheets.
10
+ - **SSR/Node Compatibility:** Added checks for `window`, `document`, and `Node` throughout the core to ensure LegoDOM can be imported and executed in Node.js/SSR environments without crashing.
11
+ - **Global Export:** `Lego` is now correctly exported to `global` in Node.js environments.
12
+
4
13
  ## [2.0.4-alpha] - 2026-01-20
5
14
 
6
15
  ### Fixes
package/main.js CHANGED
@@ -976,33 +976,38 @@ const Lego = (() => {
976
976
  registry[t.getAttribute('b-id')] = t;
977
977
  });
978
978
 
979
- const observer = new MutationObserver(m => m.forEach(r => {
980
- r.addedNodes.forEach(n => {
981
- if (n.nodeType === Node.ELEMENT_NODE) {
982
- snap(n);
983
- // Auto-Discovery: Check if tag is unknown and loader is configured
984
- const tagName = n.tagName.toLowerCase();
985
- if (tagName.includes('-') && !registry[tagName] && config.loader && !activeBlocks.has(n)) {
986
- const result = config.loader(tagName);
987
- if (result) {
988
- // Handle Promise (user does custom fetch) vs String (we fetch)
989
- const promise = (typeof result === 'string')
990
- ? fetch(result).then(r => r.text())
991
- : result;
992
-
993
- Promise.resolve(promise)
994
- .then(legoFile => publicAPI.defineLegoFile(legoFile, tagName + '.lego'))
995
- .catch(e => console.error(`[Lego] Failed to load ${tagName}:`, e));
996
- }
997
- }
979
+ const checkAndLoad = (n) => {
980
+ if (n.nodeType !== Node.ELEMENT_NODE) return;
981
+ snap(n);
982
+ // Auto-Discovery: Check if tag is unknown and loader is configured
983
+ const tagName = n.tagName.toLowerCase();
984
+ if (tagName.includes('-') && !registry[tagName] && config.loader && !activeBlocks.has(n)) {
985
+ const result = config.loader(tagName);
986
+ if (result) {
987
+ const promise = (typeof result === 'string')
988
+ ? fetch(result).then(r => r.text())
989
+ : result;
990
+
991
+ Promise.resolve(promise)
992
+ .then(legoFile => publicAPI.defineLegoFile(legoFile, tagName + '.lego'))
993
+ .catch(e => console.error(`[Lego] Failed to load ${tagName}:`, e));
998
994
  }
999
- });
995
+ }
996
+ };
997
+
998
+ const observer = new MutationObserver(m => m.forEach(r => {
999
+ r.addedNodes.forEach(checkAndLoad);
1000
1000
  r.removedNodes.forEach(n => n.nodeType === Node.ELEMENT_NODE && unsnap(n));
1001
1001
  }));
1002
1002
  observer.observe(root, { childList: true, subtree: true });
1003
1003
 
1004
+ // Initial Scan for everything
1005
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
1006
+ checkAndLoad(root);
1007
+ while (walker.nextNode()) checkAndLoad(walker.currentNode);
1008
+
1004
1009
  root._studs = Lego.globals;
1005
- snap(root);
1010
+ // snap(root); // parsing moved to checkAndLoad
1006
1011
  bind(root, root);
1007
1012
  render(root);
1008
1013
 
package/main.min.js CHANGED
@@ -1,13 +1,7 @@
1
- const Lego = (() => {
2
- 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) => {
3
- if (/\b(function|eval|import|class|module|deploy|constructor|__proto__)\b/.test(e)) { console.warn(`[Lego] Security Warning: Blocked dangerous expression "${e}"`); return } try {
4
- const o = s.state || {}; let t = H.get(e); t || (t = new Function("global", "self", "event", "helpers", `
1
+ const Lego=(()=>{const y={},F=new WeakMap,S=new WeakMap,$=new WeakMap,L=new Set,z=new Map,U=new Map;class te{constructor(s=1e3){this.limit=s,this.cache=new Map}get(s){if(!this.cache.has(s))return;const a=this.cache.get(s);return this.cache.delete(s),this.cache.set(s,a),a}set(s,a){if(this.cache.has(s))this.cache.delete(s);else if(this.cache.size>=this.limit){const n=this.cache.keys().next().value;this.cache.delete(n)}this.cache.set(s,a)}get size(){return this.cache.size}clear(){this.cache.clear()}}const G=new te(1e3),K=new Map;let J={};const h={onError:(e,s,a)=>{console.error(`[Lego Error] [${s}]`,e,a)},metrics:{},syntax:"brackets"},x=()=>h.syntax==="brackets"?["[[","]]"]:["{{","}}"],k=()=>{const[e,s]=x(),a=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),n=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`${a}(.*?)${n}`,"g")},q=[],le=e=>typeof e!="string"?e:e.replace(/[&<>"']/g,s=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[s]),se=e=>{const a=e.split("/").pop().replace(/\.lego$/,"").replace(/_/g,"-").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase();if(!a.includes("-"))throw new Error(`[Lego] Invalid block definition: "${e}". Block names must contain a hyphen (e.g. user-card.lego or UserCard.lego).`);return a},ne=(e,s)=>{if(typeof e=="function"){const n=Array.from(document.querySelectorAll("*")).filter(t=>t.tagName.includes("-"));return[].concat(e(n))}if(e.startsWith("#")){const n=document.getElementById(e.slice(1));return n?[n]:[]}const a=s?.querySelectorAll(e)||[];return a.length>0?[...a]:[...document.querySelectorAll(e)]},j=(e,...s)=>a=>{const n=async(t,c=null,r=!0,o={})=>{if(r){const d={legoTargets:s.filter(l=>typeof l=="string"),method:t,body:c};history.pushState(d,"",e)}await I(s.length?s:null,a)};return{get:(t=!0,c={})=>n("GET",null,t,c),post:(t,c=!0,r={})=>n("POST",t,c,r),put:(t,c=!0,r={})=>n("PUT",t,c,r),patch:(t,c=!0,r={})=>n("PATCH",t,c,r),delete:(t=!0,c={})=>n("DELETE",null,t,c)}},oe=(()=>{let e=!1;const s=new Set;let a=!1;const n=new Set;let t=null;const c=new Set,r=()=>{t&&clearTimeout(t),t=setTimeout(()=>{c.forEach(d=>{const l=d._studs;if(l&&typeof l.updated=="function")try{l.updated.call(l)}catch(u){console.error("[Lego] Error in updated hook:",u)}}),c.clear(),t=null},50)},o=()=>{n.size>0&&(n.forEach(d=>s.add(d)),n.clear(),!e&&s.size>0&&(e=!0,requestAnimationFrame(i)))},i=()=>{a=!0;const d=Array.from(s);s.clear(),e=!1,d.forEach(l=>{l.isConnected&&D(l)}),d.forEach(l=>c.add(l)),r(),a=!1,o()};return{add:d=>{if(d){if(a){n.add(d);return}s.add(d),!e&&(e=!0,requestAnimationFrame(i))}}}})(),w=new Map,O=new Map,ue=(e,s,a)=>{w.has(a)||w.set(a,new Set),w.get(a).add({target:e,prop:s})},M=new Map,re=e=>{const s=Array.from(M.entries()).filter(([t])=>t.startsWith("lego:")).sort((t,c)=>t[1].timestamp-c[1].timestamp);let a=0;const n=[];for(const[t,c]of s){if(a>=e)break;try{localStorage.removeItem(t),a+=c.size,n.push(t),M.delete(t)}catch(r){console.error(`[Lego] Failed to evict ${t}:`,r)}}return n},ae=(e,s,a)=>{O.has(e)&&clearTimeout(O.get(e));const n=()=>{try{const t=JSON.stringify(s),c=new Blob([t]).size;localStorage.setItem(e,t);const r=e.startsWith("lego:")?e:`lego:${e}`;M.set(r,{timestamp:Date.now(),size:c}),O.delete(e)}catch(t){if(t.name==="QuotaExceededError"){console.warn(`[Lego] Storage quota exceeded for key: ${e}`);try{const c=JSON.stringify(s),r=new Blob([c]).size,o=re(r*2);if(o.length>0){console.log(`[Lego] Evicted ${o.length} old keys, retrying save`),localStorage.setItem(e,c);const i=e.startsWith("lego:")?e:`lego:${e}`;M.set(i,{timestamp:Date.now(),size:r})}else h.onError(new Error("Storage quota exceeded and no keys available for eviction"),"quota",e)}catch{h.onError(new Error(`Critical: Could not save ${e} even after eviction`),"quota-critical",e)}}else console.error(`[Lego] Storage Error (${e}):`,t)}};a>0?O.set(e,setTimeout(n,a)):n()};typeof window<"u"&&window.addEventListener("storage",e=>{if(!(!e.key||!w.has(e.key)))try{const s=JSON.parse(e.newValue);w.get(e.key).forEach(({target:a,prop:n,el:t,batcher:c})=>{a[n]=s,t&&c&&c.add(t)})}catch{}});const C=new WeakMap,V=e=>typeof Node<"u"&&e instanceof Node,B=(e,s,a=oe)=>{if(e===null||typeof e!="object"||V(e))return e;if(F.has(e))return F.get(e);for(const c in e){const r=e[c];if(r&&r.__type==="lego-db"){let o=r._default;try{const i=localStorage.getItem(r.key);i!==null&&(o=JSON.parse(i))}catch{}e[c]=o,C.has(e)||C.set(e,{}),C.get(e)[c]={key:r.key,debounce:r._debounce},w.has(r.key)||w.set(r.key,new Set),w.get(r.key).add({target:e,prop:c,el:s,batcher:a})}}const n={get:(c,r)=>{const o=Reflect.get(c,r);return o!==null&&typeof o=="object"&&!V(o)?B(o,s,a):o},set:(c,r,o)=>{const i=c[r],d=Reflect.set(c,r,o);if(i!==o){a.add(s);const l=C.get(c);l&&l[r]&&ae(l[r].key,o,l[r].debounce)}return d},deleteProperty:(c,r)=>{const o=Reflect.deleteProperty(c,r);return a.add(s),o}},t=new Proxy(e,n);return F.set(e,t),t},X=(e,s={})=>{try{return new Function("scope","global",`with(global) { with(scope) { return (${e}); } }`)(s,Lego.globals)}catch{return{}}},_=e=>(S.has(e)||S.set(e,{snapped:!1,bindings:null,bound:!1,rendering:!1,anchor:null,hasGlobalDependency:!1}),S.get(e)),H=(e,s)=>{if(!e)return"";const a=e.trim().split(".");let n=s;for(const t of a){if(n==null)return"";n=n[t]}return n??""},P=(e,s)=>{if(!e)return;let a=e.parentElement||(e.getRootNode?e.getRootNode().host:null);for(;a;){const n=a.tagName?a.tagName.toLowerCase():"";if(n&&(s==="*"&&y[n]||n===s.toLowerCase()))return a;a=a.parentElement||a.getRootNode&&a.getRootNode().host}},b=(e,s,a=!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 n=s.state||{};let t=G.get(e);t||(t=new Function("global","self","event","helpers",`
5
2
  with(helpers) {
6
3
  with(this) {
7
4
  return ${e}
8
5
  }
9
6
  }
10
- `), 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
11
- } catch (o) { if (c) throw o; p.onError(o, "render-error", s.self); return }
12
- }, 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.defineLegoFile(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-stylesheets=["']([^"']+)["']/); _ && (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-stylesheets", 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-stylesheets", 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) }, getActiveBlocksCount: () => 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
13
- })(); typeof window < "u" && (window.Lego = Lego);
7
+ `),G.set(e,t));const c={$ancestors:o=>P(s.self,o),$registry:o=>U.get(o.toLowerCase()),$element:s.self,$route:Lego.globals.$route,$go:(o,...i)=>j(o,...i)(s.self),$emit:(o,i)=>{s.self.dispatchEvent(new CustomEvent(o,{detail:i,bubbles:!0,composed:!0}))}},r=t.call(n,s.global,s.self,s.event,c);return typeof r=="function"?r.call(n,s.event):r}catch(n){if(a)throw n;h.onError(n,"render-error",s.self);return}},Q=(e,s)=>{if(e.type==="checkbox")e.checked!==!!s&&(e.checked=!!s);else{const a=s==null?"":String(s);e.value!==a&&(e.value=a)}},W=(e,s,a=null)=>{const n=s._studs,t=o=>{const i=_(o);if(!i.bound){if(o.hasAttributes()){const d=o.attributes;for(let l=0;l<d.length;l++){const u=d[l];if(u.name.startsWith("@")){const g=u.name.slice(1);o.addEventListener(g,p=>{try{let f=n;if(a){const E=b(a.listName,{state:n,global:Lego.globals,self:s})[a.index];f=Object.assign(Object.create(n),{[a.name]:E})}b(u.value,{state:f,global:Lego.globals,self:o,event:p},!0)}catch(f){h.onError(f,"event-handler",o)}})}}if(o.hasAttribute("b-sync")){const l=o.getAttribute("b-sync"),u=()=>{try{let g,p;if(a&&l.startsWith(a.name+".")){const E=b(a.listName,{state:n,global:Lego.globals,self:s})[a.index];if(!E)return;const N=l.split(".").slice(1);p=N.pop(),g=N.reduce((T,v)=>T[v],E)}else{const m=l.split(".");p=m.pop(),g=m.reduce((E,N)=>E[N],n)}const f=o.type==="checkbox"?o.checked:o.value;g&&g[p]!==f&&(g[p]=f)}catch(g){h.onError(g,"sync-update",o)}};o.addEventListener("input",u),o.addEventListener("change",u)}if(o.hasAttribute("b-var")){const l=o.getAttribute("b-var");n.$vars&&(n.$vars[l]=o)}}i.bound=!0}};e instanceof Element&&t(e);const c=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT);let r;for(;r=c.nextNode();)t(r)},ce=e=>{const s=[],a=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT);let n;for(;n=a.nextNode();){if((r=>{let o=r.parentNode;for(;o&&o!==e;){if(o.hasAttribute&&o.hasAttribute("b-for"))return!0;o=o.parentNode}return!1})(n))continue;const c=r=>{if(/\bglobal\b/.test(r)){const o=e.host||e;_(o).hasGlobalDependency=!0}};if(n.nodeType===Node.ELEMENT_NODE){if(n.hasAttribute("b-if")){const o=n.getAttribute("b-if");c(o);const i=document.createComment(`b-if: ${o}`),d=_(n);d.anchor=i,s.push({type:"b-if",node:n,anchor:i,expr:o})}if(n.hasAttribute("b-show")){const o=n.getAttribute("b-show");c(o),s.push({type:"b-show",node:n,expr:o})}if(n.hasAttribute("b-for")){const o=n.getAttribute("b-for").match(/^\s*(\w+)\s+in\s+([\s\S]+?)\s*$/);o&&(c(o[2]),s.push({type:"b-for",node:n,itemName:o[1],listName:o[2].trim(),template:n.cloneNode(!0)}),n.innerHTML="")}if(n.hasAttribute("b-text")&&s.push({type:"b-text",node:n,path:n.getAttribute("b-text")}),n.hasAttribute("b-html")){const o=n.getAttribute("b-html");c(o),s.push({type:"b-html",node:n,expr:o})}n.hasAttribute("b-sync")&&s.push({type:"b-sync",node:n});const[r]=x();[...n.attributes].forEach(o=>{o.value.includes(r)&&(c(o.value),s.push({type:"attr",node:n,attrName:o.name,template:o.value}))})}else if(n.nodeType===Node.TEXT_NODE){const[r]=x();n.textContent.includes(r)&&(c(n.textContent),s.push({type:"text",node:n,template:n.textContent}))}}return s},ie=(e,s)=>{const a=c=>{if(c.nodeType===Node.TEXT_NODE){c._tpl===void 0&&(c._tpl=c.textContent);const r=c._tpl.replace(k(),(o,i)=>b(i.trim(),{state:s,global:Lego.globals,self:c})??"");c.textContent!==r&&(c.textContent=r)}else if(c.nodeType===Node.ELEMENT_NODE){const[r]=x();[...c.attributes].forEach(o=>{if(o._tpl===void 0&&(o._tpl=o.value),o._tpl.includes(r)){const i=o._tpl.replace(k(),(d,l)=>b(l.trim(),{state:s,global:Lego.globals,self:c})??"");o.value!==i&&(o.value=i,o.name==="class"&&(c.className=i))}})}};a(e);const n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT);let t;for(;t=n.nextNode();)a(t)},D=e=>{const s=e._studs;if(!s)return;const a=_(e);if(!a.rendering){a.rendering=!0,h.metrics&&h.metrics.onRenderStart&&h.metrics.onRenderStart(e);try{const n=e.shadowRoot||e;a.bindings||(a.bindings=ce(n)),a.bindings.forEach(t=>{if(t.type==="b-if"){const c=!!b(t.expr,{state:s,global:Lego.globals,self:t.node}),r=!!t.node.parentNode;c&&!r?t.anchor.parentNode&&t.anchor.parentNode.replaceChild(t.node,t.anchor):!c&&r&&t.node.parentNode.replaceChild(t.anchor,t.node)}if(t.type==="b-show"&&(t.node.style.display=b(t.expr,{state:s,global:Lego.globals,self:t.node})?"":"none"),t.type==="b-text"&&(t.node.textContent=H(t.path,s)),t.type==="b-html"&&(t.node.innerHTML=b(t.expr,{state:s,global:Lego.globals,self:t.node})||""),t.type==="b-sync"&&Q(t.node,H(t.node.getAttribute("b-sync"),s)),t.type==="text"){const c=t.template.replace(k(),(r,o)=>b(o.trim(),{state:s,global:Lego.globals,self:t.node})??"");t.node.textContent!==c&&(t.node.textContent=c)}if(t.type==="attr"){const c=t.template.replace(k(),(r,o)=>b(o.trim(),{state:s,global:Lego.globals,self:t.node})??"");t.node.getAttribute(t.attrName)!==c&&(t.node.setAttribute(t.attrName,c),t.attrName==="class"&&(t.node.className=c))}if(t.type==="b-for"){const c=b(t.listName,{state:s,global:Lego.globals,self:e})||[];$.has(t.node)||$.set(t.node,new Map);const r=$.get(t.node),o=new Set;c.forEach((i,d)=>{const l=i&&typeof i=="object"?i.__id||(i.__id=Math.random()):`${d}-${i}`;o.add(l);let u=r.get(l);u||(u=t.template.cloneNode(!0),u.removeAttribute("b-for"),r.set(l,u),W(u,e,{name:t.itemName,listName:t.listName,index:d}));const g=Object.assign(Object.create(s),{[t.itemName]:i});ie(u,g),u.querySelectorAll("[b-sync]").forEach(p=>{const f=p.getAttribute("b-sync");if(f.startsWith(t.itemName+".")){const m=b(t.listName,{state:s,global:Lego.globals,self:e});Q(p,H(f.split(".").slice(1).join("."),m[d]))}}),t.node.children[d]!==u&&t.node.insertBefore(u,t.node.children[d]||null)});for(const[i,d]of r.entries())o.has(i)||(d.remove(),r.delete(i))}}),s===Lego.globals&&L.forEach(t=>{_(t).hasGlobalDependency&&D(t)})}catch(n){h.onError(n,"render",e)}finally{h.metrics&&h.metrics.onRenderEnd&&h.metrics.onRenderEnd(e),a.rendering=!1}}},A=e=>{if(!e||e.nodeType!==Node.ELEMENT_NODE)return;const s=_(e),a=e.tagName.toLowerCase(),n=y[a];if(n&&!s.snapped){s.snapped=!0;const c=n.content.cloneNode(!0),r=e.attachShadow({mode:"open"}),o=(n.getAttribute("b-stylesheets")||"").split(/\s+/).filter(Boolean);if(o.length>0){const f=o.flatMap(m=>K.get(m)||[]);f.length>0&&(r.adoptedStyleSheets=[...f])}const i=P(e,"*")||P(e.getRootNode().host,"*"),d=i&&i.state?i.state:{},l=z.get(a)||{},u=X(n.getAttribute("b-logic")||n.getAttribute("b-data")||"{}"),g=X(e.getAttribute("b-logic")||e.getAttribute("b-data")||"{}",d);e._studs=B({...l,...u,...g,$vars:{},$element:e,get $parent(){return P(e,"*")},$emit:(f,m)=>{e.dispatchEvent(new CustomEvent(f,{detail:m,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(c);const p=r.querySelector("style");if(p&&(p.textContent=p.textContent.replace(/\bself\b/g,":host")),W(r,e),L.add(e),D(e),[...r.children].forEach(A),typeof e._studs.mounted=="function")try{e._studs.mounted.call(e._studs)}catch(f){h.onError(f,"mounted",e)}}let t=e.parentElement;for(;t&&!t._studs;)t=t.parentElement;t&&t._studs&&W(e,t),[...e.children].forEach(A)},R=e=>{if(e._studs&&typeof e._studs.unmounted=="function")try{e._studs.unmounted.call(e._studs)}catch(a){console.error("[Lego] Error in unmounted:",a)}e.shadowRoot&&[...e.shadowRoot.children].forEach(R),L.delete(e),e._studs&&(e._studs.$element=null,e._studs.$vars&&(Object.keys(e._studs.$vars).forEach(a=>{e._studs.$vars[a]=null}),e._studs.$vars=null),e._studs=null),e.hasOwnProperty("state")&&delete e.state;const s=S.get(e);if(s&&(s.bindings&&(s.bindings.forEach(a=>{a.node=null,a.anchor=null}),s.bindings=null),s.anchor=null,S.delete(e)),$.has(e)){const a=$.get(e);a.forEach((n,t)=>{n&&n._studs&&(n._studs=null)}),a.clear(),$.delete(e)}[...e.children].forEach(R)},I=async(e=null,s=null)=>{const a=window.location.pathname,n=window.location.search,t=q.find(d=>d.regex.test(a));if(!t)return;let c=[];if(e)c=e.flatMap(d=>ne(d,s));else{const d=document.querySelector("lego-router");d&&(c=[d])}if(c.length===0)return;const r=a.match(t.regex).slice(1),o=Object.fromEntries(t.paramNames.map((d,l)=>[d,r[l]])),i=Object.fromEntries(new URLSearchParams(n));t.middleware&&!await t.middleware(o,Lego.globals)||(Lego.globals.$route.url=a+n,Lego.globals.$route.route=t.path,Lego.globals.$route.params=o,Lego.globals.$route.query=i,Lego.globals.$route.method=history.state?.method||"GET",Lego.globals.$route.body=history.state?.body||null,c.forEach(d=>{if(d){const l=document.createElement(t.tagName);d.replaceChildren(l)}}))},Z=e=>({__type:"lego-db",key:e,_default:void 0,_debounce:0,default(s){return this._default=s,this},debounce(s){return this._debounce=s,this}}),Y={db:Z,snap:A,unsnap:R,init:async(e=document.body,s={})=>{(!e||typeof e.nodeType!="number")&&(e=document.body),J=s.styles||{},h.loader=s.loader;const a=Object.entries(J).map(async([r,o])=>{const i=await Promise.all(o.map(async d=>{try{const u=await(await fetch(d)).text(),g=new CSSStyleSheet;return await g.replace(u),g}catch(l){return console.error(`[Lego] Failed to load stylesheet: ${d}`,l),null}}));K.set(r,i.filter(d=>d!==null))});await Promise.all(a),document.querySelectorAll("template[b-id]").forEach(r=>{y[r.getAttribute("b-id")]=r});const n=r=>{if(r.nodeType!==Node.ELEMENT_NODE)return;A(r);const o=r.tagName.toLowerCase();if(o.includes("-")&&!y[o]&&h.loader&&!L.has(r)){const i=h.loader(o);if(i){const d=typeof i=="string"?fetch(i).then(l=>l.text()):i;Promise.resolve(d).then(l=>Y.defineLegoFile(l,o+".lego")).catch(l=>console.error(`[Lego] Failed to load ${o}:`,l))}}};new MutationObserver(r=>r.forEach(o=>{o.addedNodes.forEach(n),o.removedNodes.forEach(i=>i.nodeType===Node.ELEMENT_NODE&&R(i))})).observe(e,{childList:!0,subtree:!0});const c=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT);for(n(e);c.nextNode();)n(c.currentNode);if(e._studs=Lego.globals,W(e,e),D(e),s.studio){if(!y["lego-studio"]){const r=document.createElement("script");r.src="https://unpkg.com/@legodom/studio@0.0.2/dist/lego-studio.js",r.onerror=()=>console.warn("[Lego] Failed to load Studio from CDN"),document.head.appendChild(r)}Lego.route("/_/studio","lego-studio"),Lego.route("/_/studio/:block","lego-studio")}q.length>0&&(window.addEventListener("popstate",r=>{const o=r.state?.legoTargets||null;I(o)}),document.addEventListener("submit",r=>{r.preventDefault()}),document.addEventListener("click",r=>{const i=r.composedPath().find(d=>d.tagName==="A"&&(d.hasAttribute("b-target")||d.hasAttribute("b-link")));if(i){r.preventDefault();const d=i.getAttribute("href"),l=i.getAttribute("b-target"),u=l?l.split(/\s+/).filter(Boolean):[],g=i.getAttribute("b-link")!=="false";Lego.globals.$go(d,...u).get(g)}}),I())},globals:B({$route:{url:typeof window<"u"?window.location.pathname:"/",route:"",params:{},query:{},method:"GET",body:null},$go:(e,...s)=>j(e,...s)(document.body),$db:Z},typeof document<"u"?document.body:null),defineLegoFile:(e,s="block.lego")=>{let a="",n="{}",t="",c="",r=e;const o=/<(template|script|style)\b((?:\s+(?:[^>"']|"[^"]*"|'[^']*')*)*)>/i;for(;r;){const l=r.match(o);if(!l)break;const u=l[1].toLowerCase(),g=l[2],p=l[0],f=l.index,m=`</${u}>`,E=f+p.length,N=r.indexOf(m,E);if(N===-1){console.warn(`[Lego] Unclosed <${u}> tag in ${s}`);break}const T=r.slice(E,N);if(u==="template"){a=T.trim();const v=g.match(/b-stylesheets=["']([^"']+)["']/);v&&(t=v[1])}else if(u==="script"){const v=T.trim(),ee=v.match(/export\s+default\s+({[\s\S]*})/);n=ee?ee[1]:v}else u==="style"&&(c=T.trim());r=r.slice(N+m.length)}const i=se(s),d=new Function(`return ${n}`)();c&&(a=`<style>${c}</style>`+a),y[i]=document.createElement("template"),y[i].innerHTML=a,y[i].setAttribute("b-stylesheets",t),z.set(i,d),document.querySelectorAll(i).forEach(l=>!_(l).snapped&&A(l))},block:(e,s,a={},n="")=>{const t=document.createElement("template");t.setAttribute("b-id",e),t.setAttribute("b-stylesheets",n),t.innerHTML=s,y[e]=t,z.set(e,a);try{U.set(e.toLowerCase(),B({...a},document.body))}catch(c){h.onError(c,"define",e)}document.querySelectorAll(e).forEach(A)},get define(){return this.block},getActiveBlocksCount:()=>L.size,getLegos:()=>Object.keys(y),config:h,route:(e,s,a=null)=>{const n=[],t=e.replace(/:([^\/]+)/g,(c,r)=>(n.push(r),"([^/]+)"));q.push({path:e,regex:new RegExp(`^${t}$`),tagName:s,paramNames:n,middleware:a})}};return Y})();typeof window<"u"?window.Lego=Lego:typeof global<"u"&&(global.Lego=Lego);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lego-dom",
3
- "version": "2.0.4-alpha",
3
+ "version": "2.0.4-beta",
4
4
  "license": "MIT",
5
5
  "description": "A feature-rich web components + SFC frontend framework",
6
6
  "main": "main.js",