lego-dom 1.3.4 → 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.
Files changed (93) hide show
  1. package/CHANGELOG.md +72 -1
  2. package/main.js +48 -17
  3. package/main.min.js +2 -2
  4. package/package.json +1 -1
  5. package/parse-lego.js +2 -2
  6. package/vite-plugin.js +0 -14
  7. package/.github/workflows/deploy-docs.yml +0 -56
  8. package/.legodom +0 -87
  9. package/docs/.vitepress/config.js +0 -161
  10. package/docs/api/config.md +0 -95
  11. package/docs/api/define.md +0 -58
  12. package/docs/api/directives.md +0 -50
  13. package/docs/api/globals.md +0 -29
  14. package/docs/api/index.md +0 -30
  15. package/docs/api/lifecycle.md +0 -40
  16. package/docs/api/route.md +0 -37
  17. package/docs/api/vite-plugin.md +0 -58
  18. package/docs/contributing/01-welcome.md +0 -38
  19. package/docs/contributing/02-registry.md +0 -133
  20. package/docs/contributing/03-batcher.md +0 -110
  21. package/docs/contributing/04-reactivity.md +0 -87
  22. package/docs/contributing/05-caching.md +0 -59
  23. package/docs/contributing/06-init.md +0 -136
  24. package/docs/contributing/07-observer.md +0 -72
  25. package/docs/contributing/08-snap.md +0 -140
  26. package/docs/contributing/09-diffing.md +0 -69
  27. package/docs/contributing/10-studs.md +0 -78
  28. package/docs/contributing/11-scanner.md +0 -117
  29. package/docs/contributing/12-render.md +0 -138
  30. package/docs/contributing/13-directives.md +0 -243
  31. package/docs/contributing/14-events.md +0 -57
  32. package/docs/contributing/15-router.md +0 -57
  33. package/docs/contributing/16-state.md +0 -47
  34. package/docs/contributing/17-legodom.md +0 -48
  35. package/docs/contributing/index.md +0 -24
  36. package/docs/examples/form.md +0 -42
  37. package/docs/examples/index.md +0 -104
  38. package/docs/examples/routing.md +0 -409
  39. package/docs/examples/sfc-showcase.md +0 -34
  40. package/docs/examples/todo-app.md +0 -383
  41. package/docs/guide/cdn-usage.md +0 -354
  42. package/docs/guide/components.md +0 -418
  43. package/docs/guide/directives.md +0 -539
  44. package/docs/guide/directory-structure.md +0 -248
  45. package/docs/guide/faq.md +0 -210
  46. package/docs/guide/getting-started.md +0 -262
  47. package/docs/guide/index.md +0 -88
  48. package/docs/guide/lifecycle.md +0 -525
  49. package/docs/guide/quick-start.md +0 -49
  50. package/docs/guide/reactivity.md +0 -415
  51. package/docs/guide/routing.md +0 -334
  52. package/docs/guide/server-side.md +0 -134
  53. package/docs/guide/sfc.md +0 -464
  54. package/docs/guide/templating.md +0 -388
  55. package/docs/index.md +0 -160
  56. package/docs/public/logo.svg +0 -17
  57. package/docs/router/basic-routing.md +0 -103
  58. package/docs/router/cold-entry.md +0 -91
  59. package/docs/router/history.md +0 -69
  60. package/docs/router/index.md +0 -73
  61. package/docs/router/resolver.md +0 -74
  62. package/docs/router/surgical-swaps.md +0 -134
  63. package/docs/tutorial/01-project-setup.md +0 -152
  64. package/docs/tutorial/02-your-first-component.md +0 -226
  65. package/docs/tutorial/03-adding-routes.md +0 -279
  66. package/docs/tutorial/04-multi-page-app.md +0 -329
  67. package/docs/tutorial/05-state-and-globals.md +0 -285
  68. package/docs/tutorial/index.md +0 -40
  69. package/examples/vite-app/README.md +0 -71
  70. package/examples/vite-app/index.html +0 -42
  71. package/examples/vite-app/package.json +0 -18
  72. package/examples/vite-app/src/app.css +0 -3
  73. package/examples/vite-app/src/app.js +0 -29
  74. package/examples/vite-app/src/components/app-navbar.lego +0 -34
  75. package/examples/vite-app/src/components/customers/customer-details.lego +0 -24
  76. package/examples/vite-app/src/components/customers/customer-orders.lego +0 -21
  77. package/examples/vite-app/src/components/customers/order-list.lego +0 -55
  78. package/examples/vite-app/src/components/greeting-card.lego +0 -41
  79. package/examples/vite-app/src/components/sample-component.lego +0 -75
  80. package/examples/vite-app/src/components/shells/customers-shell.lego +0 -21
  81. package/examples/vite-app/src/components/side-menu.lego +0 -46
  82. package/examples/vite-app/src/components/todo-list.lego +0 -239
  83. package/examples/vite-app/src/components/widgets/user-card.lego +0 -27
  84. package/examples/vite-app/vite.config.js +0 -22
  85. package/tests/error.test.js +0 -74
  86. package/tests/main.test.js +0 -103
  87. package/tests/memory.test.js +0 -68
  88. package/tests/monitoring.test.js +0 -74
  89. package/tests/naming.test.js +0 -74
  90. package/tests/parse-lego.test.js +0 -65
  91. package/tests/security.test.js +0 -67
  92. package/tests/server.test.js +0 -114
  93. package/tests/syntax.test.js +0 -67
package/CHANGELOG.md CHANGED
@@ -1,6 +1,77 @@
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.
20
+
21
+ ## [1.4.0] - 2026-01-16
22
+
23
+ ### Breaking Changes
24
+
25
+ - **`$ancestors()` now returns element instead of state:** For consistency with `$element`, `$ancestors()` now returns the ancestor element. Access state via `.state` property.
26
+ ```javascript
27
+ // Before (v1.3.x)
28
+ const userId = this.$ancestors('user-profile').userId;
29
+
30
+ // After (v1.4.0)
31
+ const userId = this.$ancestors('user-profile').state.userId;
32
+ ```
33
+ **Migration:** Add `.state` after all `$ancestors()` calls that access component state.
34
+
35
+ ## [1.3.5] - 2026-01-16
36
+
37
+ ### Features
38
+
39
+ - **Lego Studio:** Added zero-config component development environment. Enable with `Lego.init(document.body, { studio: true })` and navigate to `/_/studio` to browse, preview, and inspect components in real-time.
40
+ - Component browser with search/filter
41
+ - Live component preview in isolation
42
+ - Real-time state inspector with JSON editor
43
+ - URL deep linking for sharing components
44
+ - Published as separate package: `@legodom/studio@0.0.2`
45
+
46
+ - **Advanced API:** Exposed `Lego.snap()` and `Lego.unsnap()` for manual component initialization and cleanup. Useful for building dev tools, testing, and dynamic component creation in Shadow DOMs.
47
+ ```javascript
48
+ const el = document.createElement('my-component');
49
+ mount.appendChild(el);
50
+ Lego.snap(el); // Manually initialize
51
+
52
+ // Later...
53
+ Lego.unsnap(el); // Clean up
54
+ ```
55
+
56
+ - **Component State Property:** Added `element.state` getter/setter to all component instances for cleaner state access.
57
+ ```javascript
58
+ // Before (internal API)
59
+ const state = element._studs;
60
+
61
+ // After (public API)
62
+ const state = element.state;
63
+ element.state = { count: 5 }; // Merges into existing state
64
+ ```
65
+
66
+ ### Documentation
67
+
68
+ - **Advanced API Guide:** Added comprehensive documentation for `Lego.snap()` and `Lego.unsnap()` at `/docs/api/advanced.md`
69
+ - **Lego Studio Guide:** Added full Studio documentation at `/docs/guide/studio.md` with quick start, examples, and troubleshooting
70
+
71
+ ### Improvements
72
+
73
+ - **Studio CDN Loading:** Studio is loaded on-demand from unpkg CDN when `studio: true` is set, keeping core LegoDOM lightweight
74
+ - **State Reactivity:** The `element.state` setter uses `Object.assign()` to merge new values while preserving the reactive Proxy
4
75
 
5
76
  ## [1.3.4] - 2026-01-16
6
77
 
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
  };
@@ -193,11 +203,11 @@ const Lego = (() => {
193
203
  return current ?? '';
194
204
  };
195
205
 
196
- const findAncestorState = (el, tagName) => {
206
+ const findAncestor = (el, tagName) => {
197
207
  let parent = el.parentElement || el.getRootNode().host;
198
208
  while (parent) {
199
209
  if (parent.tagName && parent.tagName.toLowerCase() === tagName.toLowerCase()) {
200
- return parent._studs;
210
+ return parent;
201
211
  }
202
212
  parent = parent.parentElement || (parent.getRootNode && parent.getRootNode().host);
203
213
  }
@@ -231,7 +241,7 @@ const Lego = (() => {
231
241
  }
232
242
 
233
243
  const helpers = {
234
- $ancestors: (tag) => findAncestorState(context.self, tag),
244
+ $ancestors: (tag) => findAncestor(context.self, tag),
235
245
  $registry: (tag) => sharedStates.get(tag.toLowerCase()),
236
246
  $element: context.self,
237
247
  $route: Lego.globals.$route,
@@ -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) {
@@ -568,6 +578,13 @@ const Lego = (() => {
568
578
  get $go() { return Lego.globals.$go }
569
579
  }, el);
570
580
 
581
+ Object.defineProperty(el, 'state', {
582
+ get() { return this._studs },
583
+ set(v) { Object.assign(this._studs, v) },
584
+ configurable: true,
585
+ enumerable: false
586
+ });
587
+
571
588
  shadow.appendChild(tpl);
572
589
 
573
590
  const style = shadow.querySelector('style');
@@ -649,6 +666,8 @@ const Lego = (() => {
649
666
  };
650
667
 
651
668
  const publicAPI = {
669
+ snap,
670
+ unsnap,
652
671
  init: async (root = document.body, options = {}) => {
653
672
  if (!root || typeof root.nodeType !== 'number') root = document.body;
654
673
  styleConfig = options.styles || {};
@@ -706,6 +725,17 @@ const Lego = (() => {
706
725
  bind(root, root);
707
726
  render(root);
708
727
 
728
+ if (options.studio) {
729
+ if (!registry['lego-studio']) {
730
+ const script = document.createElement('script');
731
+ script.src = 'https://unpkg.com/@legodom/studio@0.0.2/dist/lego-studio.js';
732
+ script.onerror = () => console.warn('[Lego] Failed to load Studio from CDN');
733
+ document.head.appendChild(script);
734
+ }
735
+ Lego.route('/_/studio', 'lego-studio');
736
+ Lego.route('/_/studio/:component', 'lego-studio');
737
+ }
738
+
709
739
  if (routes.length > 0) {
710
740
  // Smart History: Restore surgical targets on Back button
711
741
  window.addEventListener('popstate', (event) => {
@@ -826,6 +856,7 @@ const Lego = (() => {
826
856
  },
827
857
  // For specific test validation
828
858
  getActiveComponentsCount: () => activeComponents.size,
859
+ getLegos: () => Object.keys(registry),
829
860
  config, // Expose config for customization
830
861
  route: (path, tagName, middleware = null) => {
831
862
  const paramNames = [];
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.3.4",
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
  }
package/vite-plugin.js CHANGED
@@ -153,20 +153,6 @@ export default '${parsed.componentName}';
153
153
  }
154
154
  },
155
155
 
156
- transform(code, id) {
157
- if (id.endsWith('.lego') && !id.includes('?')) {
158
- const parsed = parseLego(code, path.basename(id));
159
- const validation = validateLego(parsed);
160
-
161
- if (!validation.valid) {
162
- throw new Error(`Invalid .lego file:\n${validation.errors.join('\n')}`);
163
- }
164
156
 
165
- return {
166
- code: generateDefineCall(parsed),
167
- map: null
168
- };
169
- }
170
- }
171
157
  };
172
158
  }
@@ -1,56 +0,0 @@
1
- name: Deploy Documentation
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- workflow_dispatch:
7
-
8
- permissions:
9
- contents: read
10
- pages: write
11
- id-token: write
12
-
13
- concurrency:
14
- group: pages
15
- cancel-in-progress: false
16
-
17
- jobs:
18
- build:
19
- runs-on: ubuntu-latest
20
- steps:
21
- - name: Checkout
22
- uses: actions/checkout@v4
23
- with:
24
- fetch-depth: 0
25
-
26
- - name: Setup Node
27
- uses: actions/setup-node@v4
28
- with:
29
- node-version: 20
30
- cache: npm
31
-
32
- - name: Setup Pages
33
- uses: actions/configure-pages@v4
34
-
35
- - name: Install dependencies
36
- run: npm ci
37
-
38
- - name: Build with VitePress
39
- run: npm run docs:build
40
-
41
- - name: Upload artifact
42
- uses: actions/upload-pages-artifact@v3
43
- with:
44
- path: docs/.vitepress/dist
45
-
46
- deploy:
47
- environment:
48
- name: github-pages
49
- url: ${{ steps.deployment.outputs.page_url }}
50
- needs: build
51
- runs-on: ubuntu-latest
52
- name: Deploy
53
- steps:
54
- - name: Deploy to GitHub Pages
55
- id: deployment
56
- uses: actions/deploy-pages@v4
package/.legodom DELETED
@@ -1,87 +0,0 @@
1
- # LegoDOM Framework Definition (.legodom)
2
-
3
- This file defines the valid syntax, API, and behavior of the LegoDOM framework. Use this context to write valid `.lego` components and understand the runtime behavior.
4
-
5
- ## 1. Component Structure (.lego)
6
- A `.lego` file is a Single File Component (SFC) composed of three sections:
7
-
8
- ```html
9
- <template b-styles="optional-style-id">
10
- <!-- HTML template with directives -->
11
- </template>
12
-
13
- <script>
14
- export default {
15
- // Reactive State
16
- count: 0,
17
-
18
- // Lifecycle Hooks
19
- mounted() { console.log('Mounted'); },
20
- updated() { console.log('State updated'); },
21
- unmounted() { console.log('Destroyed'); },
22
-
23
- // Methods
24
- increment() { this.count++ }
25
- }
26
- </script>
27
-
28
- <style>
29
- /* Scoped CSS. 'self' is replaced with ':host' automatically */
30
- self { display: block; }
31
- h1 { color: blue; }
32
- </style>
33
- ```
34
-
35
- ## 2. Template Directives
36
-
37
- | Directive | Syntax | Description |
38
- |-----------|--------|-------------|
39
- | **b-if** | `b-if="expr"` | Conditionally renders element if `expr` is truthy. Replaced by comment anchor if false. |
40
- | **b-show**| `b-show="expr"` | Toggles `display: none` based on `expr`. |
41
- | **b-for** | `b-for="item in list"` | Renders element for each item in `list`. `item` is available in scope. |
42
- | **b-text**| `b-text="path"` | Sets `textContent` to value at `path` (e.g. `user.name`). |
43
- | **b-html**| `b-html="expr"` | Sets `innerHTML` to result of `expr`. **Unsafe**. |
44
- | **b-sync**| `b-sync="path"` | Two-way binding for inputs. Updates `state.path` on input/change. |
45
- | **Event** | `@event="expr"` | Native event listener. `event` is available in scope. E.g. `@click="save()"`. |
46
- | **Interpolation** | `[[ expr ]]` | Text interpolation. Delimiters are `[[` and `]]` by default. |
47
-
48
- Note: `b-data` can be used on `<template>` or the custom element tag to inject initial state from JSON string.
49
-
50
- ## 3. Script Context & Helpers
51
- The `script` exports a default object which becomes the reactive state (`this`).
52
- Inside methods and template expressions, the following helpers are available:
53
-
54
- ### Scope
55
- - **this**: The reactive state of the component.
56
- - **event**: The native DOM event (only in `@event` handlers).
57
-
58
- ### Helpers
59
- - **$element**: The component's host DOM element.
60
- - **$emit(name, detail)**: Dispatches a CustomEvent `name` with `detail` (bubbles: true, composed: true).
61
- - **$go(path, ...targets)**: Router navigation helper. Returns object with method calls.
62
- - `this.$go('/url', '#target').get()`
63
- - `this.$go('/api', '#response').post({data})`
64
- - **$ancestors(tagName)**: Finds the state of the nearest ancestor component with `tagName`.
65
- - **$registry(tagName)**: Access shared state of logical-only components defined via `Lego.define`.
66
- - **$route**: Global route state object.
67
-
68
- ## 4. Router & Targets
69
- LegoDOM uses a "Target-Oriented" router. Navigation updates specific DOM elements (targets) rather than full page reloads.
70
-
71
- - **Definition**: `Lego.route('/path/:id', 'tag-name', middleware)`
72
- - **Targets**:
73
- - `string`: CSS selector (e.g., `#main`, `user-card`).
74
- - `function`: `(allComponents) => elements`
75
- - **Usage**:
76
- - HTML: `<a href="/path" b-target="#main">Link</a>` creates a surgical navigation link.
77
- - JS: `this.$go('/path', '#main').get()`
78
-
79
- ## 5. Lifecycle Hooks
80
- - **mounted()**: Called when the component is attached to DOM, shadow root created, and initial render complete.
81
- - **updated()**: Called after reactive state changes have been batched and applied to the DOM.
82
- - **unmounted()**: Called when component is removed from the DOM.
83
-
84
- ## 6. Global Configuration
85
- - `Lego.init(root, { styles: { ... }, loader: callback })`: Initializes the app.
86
- - `Lego.globals`: Global reactive state shared across all components.
87
- ```
@@ -1,161 +0,0 @@
1
- import { defineConfig } from 'vitepress';
2
-
3
- export default defineConfig({
4
- title: 'Lego',
5
- description: 'A feature-rich web components + SFC frontend framework',
6
- base: '/legodom/',
7
-
8
- themeConfig: {
9
- logo: '/logo.svg',
10
-
11
- nav: [
12
- { text: 'Guide', link: '/guide/' },
13
- { text: 'Tutorial', link: '/tutorial/' },
14
- { text: 'Contributing', link: '/contributing/' },
15
- { text: 'API', link: '/api/' },
16
- { text: 'Examples', link: '/examples/' },
17
- { text: 'Router', link: '/router/' },
18
- {
19
- text: 'v1.3.4',
20
- items: [
21
- { text: 'Changelog', link: 'https://github.com/rayattack/LegoDOM/releases' }
22
-
23
- ]
24
- }
25
- ],
26
-
27
- sidebar: {
28
- '/contributing/': [
29
- {
30
- text: 'Contributing',
31
- items: [
32
- { text: 'Topic 1 - Welcome', link: '/contributing/01-welcome' },
33
- { text: 'Topic 2 - The Registry', link: '/contributing/02-registry' },
34
- { text: 'Topic 3 - Batching', link: '/contributing/03-batcher' },
35
- { text: 'Topic 4 - Reactivity', link: '/contributing/04-reactivity' },
36
- { text: 'Topic 5 - Caching', link: '/contributing/05-caching' },
37
- { text: 'Topic 6 - LegoDOM Init', link: '/contributing/06-init' },
38
- { text: 'Topic 7 - Observer', link: '/contributing/07-observer' },
39
- { text: 'Topic 8 - Snap', link: '/contributing/08-snap' },
40
- { text: 'Topic 9 - Diffing', link: '/contributing/09-diffing' },
41
- { text: 'Topic 10 - State', 'link': '/contributing/10-studs' },
42
- { text: 'Topic 11 - Scanner', 'link': '/contributing/11-scanner' },
43
- { text: 'Topic 12 - Render', 'link': '/contributing/12-render' },
44
- { text: 'Topic 13 - Directives', 'link': '/contributing/13-directives' },
45
- { text: 'Topic 14 - Events', 'link': '/contributing/14-events' },
46
- { text: 'Topic 15 - Router', 'link': '/contributing/15-router' },
47
- { text: 'Topic 16 - State', 'link': '/contributing/16-state' },
48
- { text: 'Topic 17 - LegoDOM', 'link': '/contributing/17-legodom' },
49
- ]
50
- }
51
- ],
52
- '/tutorial/': [
53
- {
54
- text: 'Tutorial: Your First App',
55
- items: [
56
- { text: 'Overview', link: '/tutorial/' },
57
- { text: '1. Project Setup', link: '/tutorial/01-project-setup' },
58
- { text: '2. Your First Component', link: '/tutorial/02-your-first-component' },
59
- { text: '3. Adding Routes', link: '/tutorial/03-adding-routes' },
60
- { text: '4. Multi-Page App', link: '/tutorial/04-multi-page-app' },
61
- { text: '5. State & Globals', link: '/tutorial/05-state-and-globals' }
62
- ]
63
- }
64
- ],
65
- '/guide/': [
66
- {
67
- text: 'Introduction',
68
- items: [
69
- { text: 'What is LegoDOM?', link: '/guide/' },
70
- { text: 'Getting Started', link: '/guide/getting-started' },
71
- { text: 'Quick Start', link: '/guide/quick-start' },
72
- ]
73
- },
74
- {
75
- text: 'Core Concepts',
76
- items: [
77
- { text: 'Components', link: '/guide/components' },
78
- { text: 'Templating', link: '/guide/templating' },
79
- { text: 'Reactivity', link: '/guide/reactivity' },
80
- { text: 'Directives', link: '/guide/directives' }
81
- ]
82
- },
83
- {
84
- text: 'Advanced',
85
- items: [
86
- { text: 'Single File Components', link: '/guide/sfc' },
87
- { text: 'Routing', link: '/guide/routing' },
88
- { text: 'CDN Usage', link: '/guide/cdn-usage' },
89
- { text: 'Lifecycle Hooks', link: '/guide/lifecycle' },
90
- { text: 'Large Apps', link: '/guide/directory-structure' },
91
- { text: 'FAQ', link: '/guide/faq' }
92
- ]
93
- }
94
- ],
95
- '/api/': [
96
- {
97
- text: 'API Reference',
98
- items: [
99
- { text: 'Overview', link: '/api/' },
100
- { text: 'Lego.define()', link: '/api/define' },
101
- { text: 'Lego.route()', link: '/api/route' },
102
- { text: 'Lego.globals', link: '/api/globals' },
103
- { text: 'Lego.config', link: '/api/config' },
104
- { text: 'Directives', link: '/api/directives' },
105
- { text: 'Lifecycle Hooks', link: '/api/lifecycle' },
106
- { text: 'Vite Plugin', link: '/api/vite-plugin' }
107
- ]
108
- }
109
- ],
110
- '/router/': [
111
- {
112
- text: 'LegoDOM Router',
113
- items: [
114
- { text: 'About', link: '/router/' },
115
- { text: 'Basic Routing', link: '/router/basic-routing' },
116
- { text: 'Surgical Swaps', link: '/router/surgical-swaps' },
117
- { text: 'Cold Entry', link: '/router/cold-entry' },
118
- { text: 'History', link: '/router/history' }
119
- ]
120
- }
121
- ],
122
- '/examples/': [
123
- {
124
- text: 'Examples',
125
- items: [
126
- { text: 'Overview', link: '/examples/' },
127
- { text: 'Todo App', link: '/examples/todo-app' },
128
- { text: 'Routing Demo', link: '/examples/routing' },
129
- { text: 'SFC Showcase', link: '/examples/sfc-showcase' },
130
- { text: 'Form Validation', link: '/examples/form' }
131
- ]
132
- }
133
- ]
134
- },
135
-
136
- socialLinks: [
137
- { icon: 'github', link: 'https://github.com/rayattack/LegoDOM' }
138
- ],
139
-
140
- footer: {
141
- message: 'Released under the MIT License.',
142
- copyright: 'Copyright © 2024-present'
143
- },
144
-
145
- search: {
146
- provider: 'local'
147
- },
148
-
149
- editLink: {
150
- pattern: 'https://github.com/rayattack/LegoDOM/edit/main/docs/:path',
151
- text: 'Edit this page on GitHub'
152
- }
153
- },
154
-
155
- markdown: {
156
- theme: {
157
- light: 'github-light',
158
- dark: 'github-dark'
159
- }
160
- }
161
- });