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.
- package/CHANGELOG.md +72 -1
- package/main.js +48 -17
- package/main.min.js +2 -2
- package/package.json +1 -1
- package/parse-lego.js +2 -2
- package/vite-plugin.js +0 -14
- package/.github/workflows/deploy-docs.yml +0 -56
- package/.legodom +0 -87
- package/docs/.vitepress/config.js +0 -161
- package/docs/api/config.md +0 -95
- package/docs/api/define.md +0 -58
- package/docs/api/directives.md +0 -50
- package/docs/api/globals.md +0 -29
- package/docs/api/index.md +0 -30
- package/docs/api/lifecycle.md +0 -40
- package/docs/api/route.md +0 -37
- package/docs/api/vite-plugin.md +0 -58
- package/docs/contributing/01-welcome.md +0 -38
- package/docs/contributing/02-registry.md +0 -133
- package/docs/contributing/03-batcher.md +0 -110
- package/docs/contributing/04-reactivity.md +0 -87
- package/docs/contributing/05-caching.md +0 -59
- package/docs/contributing/06-init.md +0 -136
- package/docs/contributing/07-observer.md +0 -72
- package/docs/contributing/08-snap.md +0 -140
- package/docs/contributing/09-diffing.md +0 -69
- package/docs/contributing/10-studs.md +0 -78
- package/docs/contributing/11-scanner.md +0 -117
- package/docs/contributing/12-render.md +0 -138
- package/docs/contributing/13-directives.md +0 -243
- package/docs/contributing/14-events.md +0 -57
- package/docs/contributing/15-router.md +0 -57
- package/docs/contributing/16-state.md +0 -47
- package/docs/contributing/17-legodom.md +0 -48
- package/docs/contributing/index.md +0 -24
- package/docs/examples/form.md +0 -42
- package/docs/examples/index.md +0 -104
- package/docs/examples/routing.md +0 -409
- package/docs/examples/sfc-showcase.md +0 -34
- package/docs/examples/todo-app.md +0 -383
- package/docs/guide/cdn-usage.md +0 -354
- package/docs/guide/components.md +0 -418
- package/docs/guide/directives.md +0 -539
- package/docs/guide/directory-structure.md +0 -248
- package/docs/guide/faq.md +0 -210
- package/docs/guide/getting-started.md +0 -262
- package/docs/guide/index.md +0 -88
- package/docs/guide/lifecycle.md +0 -525
- package/docs/guide/quick-start.md +0 -49
- package/docs/guide/reactivity.md +0 -415
- package/docs/guide/routing.md +0 -334
- package/docs/guide/server-side.md +0 -134
- package/docs/guide/sfc.md +0 -464
- package/docs/guide/templating.md +0 -388
- package/docs/index.md +0 -160
- package/docs/public/logo.svg +0 -17
- package/docs/router/basic-routing.md +0 -103
- package/docs/router/cold-entry.md +0 -91
- package/docs/router/history.md +0 -69
- package/docs/router/index.md +0 -73
- package/docs/router/resolver.md +0 -74
- package/docs/router/surgical-swaps.md +0 -134
- package/docs/tutorial/01-project-setup.md +0 -152
- package/docs/tutorial/02-your-first-component.md +0 -226
- package/docs/tutorial/03-adding-routes.md +0 -279
- package/docs/tutorial/04-multi-page-app.md +0 -329
- package/docs/tutorial/05-state-and-globals.md +0 -285
- package/docs/tutorial/index.md +0 -40
- package/examples/vite-app/README.md +0 -71
- package/examples/vite-app/index.html +0 -42
- package/examples/vite-app/package.json +0 -18
- package/examples/vite-app/src/app.css +0 -3
- package/examples/vite-app/src/app.js +0 -29
- package/examples/vite-app/src/components/app-navbar.lego +0 -34
- package/examples/vite-app/src/components/customers/customer-details.lego +0 -24
- package/examples/vite-app/src/components/customers/customer-orders.lego +0 -21
- package/examples/vite-app/src/components/customers/order-list.lego +0 -55
- package/examples/vite-app/src/components/greeting-card.lego +0 -41
- package/examples/vite-app/src/components/sample-component.lego +0 -75
- package/examples/vite-app/src/components/shells/customers-shell.lego +0 -21
- package/examples/vite-app/src/components/side-menu.lego +0 -46
- package/examples/vite-app/src/components/todo-list.lego +0 -239
- package/examples/vite-app/src/components/widgets/user-card.lego +0 -27
- package/examples/vite-app/vite.config.js +0 -22
- package/tests/error.test.js +0 -74
- package/tests/main.test.js +0 -103
- package/tests/memory.test.js +0 -68
- package/tests/monitoring.test.js +0 -74
- package/tests/naming.test.js +0 -74
- package/tests/parse-lego.test.js +0 -65
- package/tests/security.test.js +0 -67
- package/tests/server.test.js +0 -114
- package/tests/syntax.test.js +0 -67
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,77 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
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
|
|
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) =>
|
|
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-
|
|
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
|
|
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=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[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
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-
|
|
60
|
-
const bStylesMatch = attrs.match(/b-
|
|
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
|
-
});
|