lego-dom 0.0.5 β 0.0.8
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/.github/workflows/deploy-docs.yml +56 -0
- package/LICENSE +21 -0
- package/README.md +298 -355
- package/docs/.vitepress/config.js +107 -0
- package/docs/.vitepress/dist/404.html +22 -0
- package/docs/.vitepress/dist/api/define.html +35 -0
- package/docs/.vitepress/dist/api/directives.html +32 -0
- package/docs/.vitepress/dist/api/globals.html +27 -0
- package/docs/.vitepress/dist/api/index.html +25 -0
- package/docs/.vitepress/dist/api/lifecycle.html +38 -0
- package/docs/.vitepress/dist/api/route.html +34 -0
- package/docs/.vitepress/dist/api/vite-plugin.html +37 -0
- package/docs/.vitepress/dist/assets/api_define.md.UA-ygUnQ.js +11 -0
- package/docs/.vitepress/dist/assets/api_define.md.UA-ygUnQ.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_directives.md.BV-D251p.js +8 -0
- package/docs/.vitepress/dist/assets/api_directives.md.BV-D251p.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_globals.md.DOjt7AV0.js +3 -0
- package/docs/.vitepress/dist/assets/api_globals.md.DOjt7AV0.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_index.md.OS6h01ct.js +1 -0
- package/docs/.vitepress/dist/assets/api_index.md.OS6h01ct.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_lifecycle.md.Ccm5xw6-.js +14 -0
- package/docs/.vitepress/dist/assets/api_lifecycle.md.Ccm5xw6-.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_route.md.CAHf_KNp.js +10 -0
- package/docs/.vitepress/dist/assets/api_route.md.CAHf_KNp.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_vite-plugin.md.DNn9VhL5.js +13 -0
- package/docs/.vitepress/dist/assets/api_vite-plugin.md.DNn9VhL5.lean.js +1 -0
- package/docs/.vitepress/dist/assets/app.BG5s3B0P.js +1 -0
- package/docs/.vitepress/dist/assets/chunks/@localSearchIndexroot.DQmuWC2Z.js +1 -0
- package/docs/.vitepress/dist/assets/chunks/VPLocalSearchBox.BO-PSxt1.js +9 -0
- package/docs/.vitepress/dist/assets/chunks/framework.B7OFBR9X.js +19 -0
- package/docs/.vitepress/dist/assets/chunks/theme.DA-iSa9B.js +2 -0
- package/docs/.vitepress/dist/assets/examples_form.md.B3stGKbu.js +34 -0
- package/docs/.vitepress/dist/assets/examples_form.md.B3stGKbu.lean.js +1 -0
- package/docs/.vitepress/dist/assets/examples_index.md.BDEG_D4J.js +30 -0
- package/docs/.vitepress/dist/assets/examples_index.md.BDEG_D4J.lean.js +1 -0
- package/docs/.vitepress/dist/assets/examples_routing.md.bqZ9DjDK.js +338 -0
- package/docs/.vitepress/dist/assets/examples_routing.md.bqZ9DjDK.lean.js +1 -0
- package/docs/.vitepress/dist/assets/examples_sfc-showcase.md.DLXaUiop.js +13 -0
- package/docs/.vitepress/dist/assets/examples_sfc-showcase.md.DLXaUiop.lean.js +1 -0
- package/docs/.vitepress/dist/assets/examples_todo-app.md.D5RhZoo5.js +297 -0
- package/docs/.vitepress/dist/assets/examples_todo-app.md.D5RhZoo5.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_cdn-usage.md.CAjf03Lr.js +182 -0
- package/docs/.vitepress/dist/assets/guide_cdn-usage.md.CAjf03Lr.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_components.md.BIFWF1Hc.js +174 -0
- package/docs/.vitepress/dist/assets/guide_components.md.BIFWF1Hc.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_contributing.md.BgbUN-Mr.js +1 -0
- package/docs/.vitepress/dist/assets/guide_contributing.md.BgbUN-Mr.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_directives.md.Bi3ynu1d.js +140 -0
- package/docs/.vitepress/dist/assets/guide_directives.md.Bi3ynu1d.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_getting-started.md.2Nr1lp2z.js +107 -0
- package/docs/.vitepress/dist/assets/guide_getting-started.md.2Nr1lp2z.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_index.md.GvZq_Yf2.js +2 -0
- package/docs/.vitepress/dist/assets/guide_index.md.GvZq_Yf2.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_lifecycle.md.B28j1OzS.js +304 -0
- package/docs/.vitepress/dist/assets/guide_lifecycle.md.B28j1OzS.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_quick-start.md.CNk3VGTF.js +33 -0
- package/docs/.vitepress/dist/assets/guide_quick-start.md.CNk3VGTF.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_reactivity.md.CVsaMaPv.js +135 -0
- package/docs/.vitepress/dist/assets/guide_reactivity.md.CVsaMaPv.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_routing.md.DSpDP25o.js +193 -0
- package/docs/.vitepress/dist/assets/guide_routing.md.DSpDP25o.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_sfc.md.CVUP66tS.js +187 -0
- package/docs/.vitepress/dist/assets/guide_sfc.md.CVUP66tS.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_templating.md.BgCGe4aa.js +119 -0
- package/docs/.vitepress/dist/assets/guide_templating.md.BgCGe4aa.lean.js +1 -0
- package/docs/.vitepress/dist/assets/index.md.xV1taCED.js +23 -0
- package/docs/.vitepress/dist/assets/index.md.xV1taCED.lean.js +1 -0
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/docs/.vitepress/dist/assets/style.eycE2Jhw.css +1 -0
- package/docs/.vitepress/dist/examples/form.html +58 -0
- package/docs/.vitepress/dist/examples/index.html +368 -0
- package/docs/.vitepress/dist/examples/routing.html +362 -0
- package/docs/.vitepress/dist/examples/sfc-showcase.html +37 -0
- package/docs/.vitepress/dist/examples/todo-app.html +321 -0
- package/docs/.vitepress/dist/guide/cdn-usage.html +206 -0
- package/docs/.vitepress/dist/guide/components.html +198 -0
- package/docs/.vitepress/dist/guide/contributing.html +25 -0
- package/docs/.vitepress/dist/guide/directives.html +164 -0
- package/docs/.vitepress/dist/guide/getting-started.html +131 -0
- package/docs/.vitepress/dist/guide/index.html +26 -0
- package/docs/.vitepress/dist/guide/lifecycle.html +328 -0
- package/docs/.vitepress/dist/guide/quick-start.html +57 -0
- package/docs/.vitepress/dist/guide/reactivity.html +159 -0
- package/docs/.vitepress/dist/guide/routing.html +217 -0
- package/docs/.vitepress/dist/guide/sfc.html +211 -0
- package/docs/.vitepress/dist/guide/templating.html +143 -0
- package/docs/.vitepress/dist/hashmap.json +1 -0
- package/docs/.vitepress/dist/index.html +47 -0
- package/docs/.vitepress/dist/logo.svg +38 -0
- package/docs/.vitepress/dist/vp-icons.css +1 -0
- package/docs/api/define.md +31 -0
- package/docs/api/directives.md +42 -0
- package/docs/api/globals.md +29 -0
- package/docs/api/index.md +29 -0
- package/docs/api/lifecycle.md +40 -0
- package/docs/api/route.md +37 -0
- package/docs/api/vite-plugin.md +58 -0
- package/docs/examples/form.md +42 -0
- package/docs/examples/index.md +104 -0
- package/docs/examples/routing.md +409 -0
- package/docs/examples/sfc-showcase.md +34 -0
- package/docs/examples/todo-app.md +383 -0
- package/docs/guide/cdn-usage.md +320 -0
- package/docs/guide/components.md +394 -0
- package/docs/guide/contributing.md +32 -0
- package/docs/guide/directives.md +430 -0
- package/docs/guide/getting-started.md +233 -0
- package/docs/guide/index.md +88 -0
- package/docs/guide/lifecycle.md +493 -0
- package/docs/guide/quick-start.md +46 -0
- package/docs/guide/reactivity.md +394 -0
- package/docs/guide/routing.md +373 -0
- package/docs/guide/sfc.md +381 -0
- package/docs/guide/templating.md +383 -0
- package/docs/index.md +126 -0
- package/docs/public/logo.svg +38 -0
- package/examples/vite-app/README.md +71 -0
- package/examples/vite-app/index.html +45 -0
- package/examples/vite-app/package.json +16 -0
- package/examples/vite-app/src/components/greeting-card.lego +41 -0
- package/examples/vite-app/src/components/sample-component.lego +75 -0
- package/examples/vite-app/src/main.js +11 -0
- package/examples/vite-app/vite.config.js +16 -0
- package/examples.js +99 -0
- package/package.json +34 -7
- package/parse-lego.js +119 -0
- package/parse-lego.test.js +107 -0
- package/vite-plugin.js +133 -0
- package/.ignore/auto.html +0 -135
- package/.ignore/test.html +0 -73
package/README.md
CHANGED
|
@@ -1,523 +1,466 @@
|
|
|
1
|
-
#
|
|
1
|
+
# LegoJS
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
LegoJS is a tiny, zero-dependency JavaScript library for building reactive Web Components directly in the browser.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The goal of LegoJS is **mental model simplicity**:
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
* No virtual DOM
|
|
8
|
+
* No compilation step required
|
|
9
|
+
* No JSX
|
|
10
|
+
* No framework-specific syntax to learn
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
You write **HTML**, add a few **directives**, and LegoJS takes care of reactivity and updates.
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
This README is intentionally designed so that a developer can understand **everything they need** about LegoJS by reading this file alone.
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
---
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
## Installation
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
## π Quickstart
|
|
20
|
-
|
|
21
|
-
### π¦ Installation
|
|
22
|
-
|
|
23
|
-
Drop the script and start building. No build step, no `npm install` fatigue.
|
|
20
|
+
The package name on npm is **`lego-dom`** (the name `legojs` was already taken).
|
|
24
21
|
|
|
22
|
+
```bash
|
|
23
|
+
npm install lego-dom
|
|
25
24
|
```
|
|
26
|
-
<script src="path/to/main.js"></script>
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
### π Creating your first Component
|
|
31
|
-
|
|
32
|
-
Define logic and layout in a standard `<template>`.
|
|
26
|
+
Or include it directly in the browser:
|
|
33
27
|
|
|
28
|
+
```html
|
|
29
|
+
<script src="node_modules/lego-dom/main.js"></script>
|
|
34
30
|
```
|
|
35
|
-
<!-- Define -->
|
|
36
|
-
<template b-id="greeting-card">
|
|
37
|
-
<style>
|
|
38
|
-
self { display: block; padding: 20px; border-radius: 8px; background: #f0f4f8; }
|
|
39
|
-
</style>
|
|
40
|
-
|
|
41
|
-
<h2>Hello, {{ name }}!</h2>
|
|
42
|
-
<button @click="showBio = !showBio">Toggle Bio</button>
|
|
43
|
-
<p b-if="showBio">{{ bio }}</p>
|
|
44
|
-
</template>
|
|
45
31
|
|
|
46
|
-
|
|
47
|
-
<greeting-card b-data="{ name: 'Alex', bio: 'Dev', showBio: false }"></greeting-card>
|
|
32
|
+
Once loaded, `Lego` is available globally.
|
|
48
33
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
## π Directives Deep Dive
|
|
34
|
+
---
|
|
52
35
|
|
|
53
|
-
|
|
36
|
+
## The Mental Model
|
|
54
37
|
|
|
55
|
-
|
|
38
|
+
Think of LegoJS like real Lego blocks:
|
|
56
39
|
|
|
57
|
-
|
|
40
|
+
* **Templates** define how a block looks
|
|
41
|
+
* **Studs** define the data attached to a block
|
|
42
|
+
* **Directives** snap data to the DOM
|
|
43
|
+
* **Changes to data automatically update the DOM**
|
|
58
44
|
|
|
59
|
-
|
|
45
|
+
There is no mounting, diffing, or reconciliation engine.
|
|
60
46
|
|
|
61
|
-
|
|
47
|
+
You change JavaScript objects β LegoJS updates the DOM.
|
|
62
48
|
|
|
63
|
-
|
|
49
|
+
---
|
|
64
50
|
|
|
65
|
-
|
|
51
|
+
## Defining a Component (Block)
|
|
66
52
|
|
|
67
|
-
`<template b-id
|
|
53
|
+
A component is defined using a standard HTML `<template>` with a `b-id`.
|
|
68
54
|
|
|
69
|
-
|
|
55
|
+
```html
|
|
56
|
+
<template b-id="hello-card">
|
|
57
|
+
<style>
|
|
58
|
+
self {
|
|
59
|
+
display: block;
|
|
60
|
+
padding: 1rem;
|
|
61
|
+
border: 1px solid #ccc;
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
70
64
|
|
|
71
|
-
|
|
65
|
+
<h2>Hello {{ name }}</h2>
|
|
66
|
+
<button @click="count++">Clicked {{ count }} times</button>
|
|
67
|
+
</template>
|
|
68
|
+
```
|
|
72
69
|
|
|
73
|
-
|
|
70
|
+
Use the component in HTML:
|
|
74
71
|
|
|
75
|
-
|
|
72
|
+
```html
|
|
73
|
+
<hello-card b-data="{ name: 'Ahmed', count: 0 }"></hello-card>
|
|
74
|
+
```
|
|
76
75
|
|
|
77
|
-
|
|
76
|
+
---
|
|
78
77
|
|
|
79
|
-
|
|
78
|
+
## Reactive State (`studs`)
|
|
80
79
|
|
|
81
|
-
|
|
80
|
+
Each component has a reactive state object internally called **studs**.
|
|
82
81
|
|
|
83
|
-
|
|
82
|
+
* Defined via `b-data` or component logic
|
|
83
|
+
* Implemented using JavaScript `Proxy`
|
|
84
|
+
* Any mutation automatically schedules a re-render
|
|
84
85
|
|
|
85
|
-
|
|
86
|
+
```html
|
|
87
|
+
<button @click="count++"></button>
|
|
88
|
+
```
|
|
86
89
|
|
|
87
|
-
|
|
90
|
+
No setters. No actions. No reducers.
|
|
88
91
|
|
|
89
|
-
|
|
92
|
+
Just mutate data.
|
|
90
93
|
|
|
91
|
-
|
|
94
|
+
---
|
|
92
95
|
|
|
93
|
-
`
|
|
96
|
+
## Templating (`{{ }}`)
|
|
94
97
|
|
|
95
|
-
|
|
98
|
+
Text interpolation works in:
|
|
96
99
|
|
|
97
|
-
|
|
100
|
+
* Text nodes
|
|
101
|
+
* Attributes
|
|
102
|
+
* Class names
|
|
98
103
|
|
|
99
|
-
|
|
104
|
+
```html
|
|
105
|
+
<p>Hello {{ user.name }}</p>
|
|
106
|
+
<img src="/avatars/{{ user.id }}.png">
|
|
107
|
+
```
|
|
100
108
|
|
|
101
|
-
|
|
109
|
+
Expressions are plain JavaScript.
|
|
102
110
|
|
|
103
|
-
|
|
111
|
+
---
|
|
104
112
|
|
|
105
|
-
|
|
113
|
+
## Event Handling (`@event`)
|
|
106
114
|
|
|
107
|
-
|
|
115
|
+
Use `@` followed by any DOM event.
|
|
108
116
|
|
|
109
|
-
|
|
117
|
+
```html
|
|
118
|
+
<button @click="submit()">Submit</button>
|
|
119
|
+
```
|
|
110
120
|
|
|
111
|
-
|
|
121
|
+
The expression runs in the componentβs state scope.
|
|
112
122
|
|
|
113
|
-
|
|
123
|
+
You also have access to:
|
|
114
124
|
|
|
115
|
-
|
|
125
|
+
* `event` β the native DOM event
|
|
126
|
+
* `$emit(name, detail)` β dispatch custom events
|
|
127
|
+
* `$element` β the host custom element
|
|
116
128
|
|
|
117
|
-
|
|
129
|
+
---
|
|
118
130
|
|
|
119
|
-
|
|
131
|
+
## Conditional Rendering (`b-if`)
|
|
120
132
|
|
|
133
|
+
```html
|
|
134
|
+
<p b-if="isLoggedIn">Welcome back</p>
|
|
121
135
|
```
|
|
122
|
-
<template b-id="todo-app">
|
|
123
|
-
<style>
|
|
124
|
-
.done { text-decoration: line-through; opacity: 0.6; }
|
|
125
|
-
ul { list-style: none; padding: 0; }
|
|
126
|
-
</style>
|
|
127
136
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
<input b-sync="newTask" placeholder="Add task...">
|
|
131
|
-
<button @click="tasks.push({text: newTask, done: false}); newTask=''">Add</button>
|
|
132
|
-
|
|
133
|
-
<ul>
|
|
134
|
-
<li b-for="task in tasks">
|
|
135
|
-
<input type="checkbox" b-sync="task.done">
|
|
136
|
-
<span class="{{ task.done ? 'done' : '' }}">{{ task.text }}</span>
|
|
137
|
-
</li>
|
|
138
|
-
</ul>
|
|
139
|
-
</template>
|
|
137
|
+
When the expression is falsy, the element is hidden via `display: none`.
|
|
140
138
|
|
|
141
|
-
|
|
142
|
-
export default {
|
|
143
|
-
tasks: [{ text: 'Native Components', done: true }, { text: 'Profit', done: false }],
|
|
144
|
-
newTask: ''
|
|
145
|
-
}
|
|
146
|
-
</script>
|
|
139
|
+
---
|
|
147
140
|
|
|
148
|
-
|
|
141
|
+
## Lists (`b-for`)
|
|
149
142
|
|
|
150
|
-
|
|
143
|
+
Render lists using `b-for`:
|
|
151
144
|
|
|
145
|
+
```html
|
|
146
|
+
<ul>
|
|
147
|
+
<li b-for="todo in todos">
|
|
148
|
+
<input type="checkbox" b-sync="todo.done">
|
|
149
|
+
<span class="{{ todo.done ? 'done' : '' }}">{{ todo.text }}</span>
|
|
150
|
+
</li>
|
|
151
|
+
</ul>
|
|
152
|
+
```
|
|
152
153
|
|
|
153
|
-
|
|
154
|
+
* DOM nodes are reused
|
|
155
|
+
* Items are tracked internally
|
|
156
|
+
* Updates are efficient without a virtual DOM
|
|
154
157
|
|
|
155
|
-
|
|
158
|
+
---
|
|
156
159
|
|
|
157
|
-
##
|
|
160
|
+
## Two-Way Binding (`b-sync`)
|
|
158
161
|
|
|
159
|
-
|
|
162
|
+
`b-sync` keeps inputs and state in sync.
|
|
160
163
|
|
|
164
|
+
```html
|
|
165
|
+
<input b-sync="username">
|
|
166
|
+
<input type="checkbox" b-sync="settings.enabled">
|
|
161
167
|
```
|
|
162
|
-
<!-- user-profile.lego -->
|
|
163
|
-
<template>
|
|
164
|
-
<div class="profile-card">
|
|
165
|
-
<img src="{{ avatar }}" alt="{{ name }}">
|
|
166
|
-
<h2>{{ name }}</h2>
|
|
167
|
-
<p>{{ bio }}</p>
|
|
168
|
-
<button @click="poke">Poke {{ name }}</button>
|
|
169
|
-
</div>
|
|
170
|
-
</template>
|
|
171
168
|
|
|
172
|
-
|
|
173
|
-
self {
|
|
174
|
-
display: block;
|
|
175
|
-
border: 1px solid #ddd;
|
|
176
|
-
border-radius: 8px;
|
|
177
|
-
padding: 1rem;
|
|
178
|
-
text-align: center;
|
|
179
|
-
}
|
|
180
|
-
img {
|
|
181
|
-
width: 80px;
|
|
182
|
-
height: 80px;
|
|
183
|
-
border-radius: 50%;
|
|
184
|
-
}
|
|
185
|
-
h2 { color: #333; }
|
|
186
|
-
</style>
|
|
169
|
+
Works with:
|
|
187
170
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
name: 'Anonymous',
|
|
193
|
-
bio: 'No bio provided.',
|
|
194
|
-
|
|
195
|
-
// Logic methods
|
|
196
|
-
poke() {
|
|
197
|
-
console.log(`${this.name} was poked!`);
|
|
198
|
-
this.$emit('poked', { name: this.name });
|
|
199
|
-
},
|
|
200
|
-
|
|
201
|
-
// Lifecycle hooks
|
|
202
|
-
mounted() {
|
|
203
|
-
console.log('Profile component is now in the DOM');
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
</script>
|
|
207
|
-
|
|
208
|
-
```
|
|
171
|
+
* text inputs
|
|
172
|
+
* checkboxes
|
|
173
|
+
* nested objects
|
|
174
|
+
* items inside `b-for`
|
|
209
175
|
|
|
210
|
-
|
|
176
|
+
---
|
|
211
177
|
|
|
212
|
-
|
|
178
|
+
## Styling and Shadow DOM
|
|
213
179
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
- **No Leakage**: Styles defined here will not leak out to the parent page, and global styles (except CSS variables) will not leak into the component.
|
|
217
|
-
|
|
180
|
+
Every component uses **Shadow DOM** automatically.
|
|
218
181
|
|
|
219
|
-
|
|
182
|
+
Inside `<style>` blocks:
|
|
220
183
|
|
|
221
|
-
|
|
184
|
+
* Use `self` to target the component root
|
|
185
|
+
* `self` is converted to `:host`
|
|
222
186
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
Lego JS provides specific hooks to manage the component's existence:
|
|
187
|
+
```css
|
|
188
|
+
self {
|
|
189
|
+
display: block;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
230
192
|
|
|
231
|
-
|
|
193
|
+
Styles never leak in or out.
|
|
232
194
|
|
|
233
|
-
|
|
195
|
+
---
|
|
234
196
|
|
|
235
|
-
|
|
197
|
+
## Lifecycle Hooks
|
|
236
198
|
|
|
237
|
-
|
|
199
|
+
Define lifecycle methods directly on the component state:
|
|
238
200
|
|
|
239
|
-
|
|
201
|
+
```js
|
|
202
|
+
{
|
|
203
|
+
mounted() {
|
|
204
|
+
console.log('Component attached');
|
|
205
|
+
},
|
|
206
|
+
updated() {
|
|
207
|
+
console.log('State changed');
|
|
208
|
+
},
|
|
209
|
+
unmounted() {
|
|
210
|
+
console.log('Component removed');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
240
214
|
|
|
241
|
-
|
|
215
|
+
---
|
|
242
216
|
|
|
243
|
-
`
|
|
217
|
+
## Custom Events (`$emit`)
|
|
244
218
|
|
|
245
|
-
|
|
219
|
+
Child components communicate upward using events.
|
|
246
220
|
|
|
247
|
-
|
|
221
|
+
```html
|
|
222
|
+
<button @click="$emit('save', data)">Save</button>
|
|
223
|
+
```
|
|
248
224
|
|
|
249
|
-
|
|
225
|
+
Events:
|
|
250
226
|
|
|
251
|
-
|
|
227
|
+
* bubble
|
|
228
|
+
* cross Shadow DOM boundaries
|
|
229
|
+
* are standard `CustomEvent`s
|
|
252
230
|
|
|
253
|
-
|
|
231
|
+
---
|
|
254
232
|
|
|
255
|
-
|
|
233
|
+
## Accessing Ancestors (`$ancestors`)
|
|
256
234
|
|
|
257
|
-
|
|
235
|
+
Read state from the nearest ancestor component:
|
|
258
236
|
|
|
259
|
-
|
|
237
|
+
```html
|
|
238
|
+
<p>{{ $ancestors('app-shell').user.name }}</p>
|
|
239
|
+
```
|
|
260
240
|
|
|
261
|
-
|
|
241
|
+
This is intended for **reading**, not mutation.
|
|
262
242
|
|
|
263
|
-
|
|
243
|
+
---
|
|
264
244
|
|
|
265
|
-
|
|
245
|
+
## Shared State (`$registry`)
|
|
266
246
|
|
|
267
|
-
|
|
247
|
+
Components defined via `Lego.define` get a shared singleton state.
|
|
268
248
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
249
|
+
```js
|
|
250
|
+
$registry('settings').theme
|
|
251
|
+
```
|
|
273
252
|
|
|
274
|
-
|
|
253
|
+
Useful for global configuration or app-wide state.
|
|
275
254
|
|
|
276
|
-
|
|
255
|
+
---
|
|
277
256
|
|
|
278
|
-
|
|
257
|
+
## Router
|
|
279
258
|
|
|
280
|
-
|
|
259
|
+
LegoJS includes a minimal client-side router.
|
|
281
260
|
|
|
282
|
-
|
|
283
|
-
<!-- child.lego: DON'T DO THIS -->
|
|
284
|
-
<button @click="$ancestors('app-shell').user.age++">Update Age</button>
|
|
261
|
+
Add a router outlet:
|
|
285
262
|
|
|
263
|
+
```html
|
|
264
|
+
<lego-router></lego-router>
|
|
286
265
|
```
|
|
287
266
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
If you have many fields, you don't need unique event handlers for each. Use a single generic update event.
|
|
291
|
-
|
|
292
|
-
1. **Child** sends the field name and the new value:
|
|
293
|
-
|
|
294
|
-
```
|
|
295
|
-
<!-- settings-child.lego -->
|
|
296
|
-
<input type="text"
|
|
297
|
-
value="{{ $ancestors('app-shell').user.name }}"
|
|
298
|
-
@input="$emit('update-field', { key: 'name', value: $self.value })">
|
|
299
|
-
|
|
300
|
-
<input type="number"
|
|
301
|
-
value="{{ $ancestors('app-shell').user.age }}"
|
|
302
|
-
@input="$emit('update-field', { key: 'age', value: $self.value })">
|
|
303
|
-
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
2. **Ancestor** handles all updates in one function:
|
|
307
|
-
|
|
308
|
-
```
|
|
309
|
-
<!-- grand-parent.lego -->
|
|
310
|
-
<template>
|
|
311
|
-
<div @update-field="handleFieldUpdate">
|
|
312
|
-
<settings-child></settings-child>
|
|
313
|
-
</div>
|
|
314
|
-
</template>
|
|
315
|
-
|
|
316
|
-
<script>
|
|
317
|
-
export default {
|
|
318
|
-
user: { name: 'John', age: 30 },
|
|
319
|
-
handleFieldUpdate(event) {
|
|
320
|
-
const { key, value } = event.detail;
|
|
321
|
-
// One place to validate everything
|
|
322
|
-
if (this.user.hasOwnProperty(key)) {
|
|
323
|
-
this.user[key] = value;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
</script>
|
|
328
|
-
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
## π The Build Process (Vite)
|
|
333
|
-
|
|
334
|
-
To use `.lego` files, you must use the `Lego.vitePlugin()`. This plugin transforms your HTML-like file into a standard JavaScript module that registers the component with the `Lego.define` API.
|
|
335
|
-
|
|
336
|
-
### Configuration
|
|
337
|
-
|
|
338
|
-
In your `vite.config.js`:
|
|
267
|
+
Define routes:
|
|
339
268
|
|
|
269
|
+
```js
|
|
270
|
+
Lego.route('/', 'home-page');
|
|
271
|
+
Lego.route('/user/:id', 'user-page');
|
|
340
272
|
```
|
|
341
|
-
import { defineConfig } from 'vite';
|
|
342
|
-
import { Lego } from './path/to/lego.js';
|
|
343
273
|
|
|
344
|
-
|
|
345
|
-
plugins: [Lego.vitePlugin()]
|
|
346
|
-
});
|
|
274
|
+
Access route params:
|
|
347
275
|
|
|
276
|
+
```html
|
|
277
|
+
<p>User ID: {{ global.params.id }}</p>
|
|
348
278
|
```
|
|
349
279
|
|
|
350
|
-
|
|
280
|
+
Navigation:
|
|
351
281
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
3. **Intent-based Events**: For complex logic (e.g., `submit-order`), use a specific event. For simple data syncing, use a generic `update-field` event.
|
|
282
|
+
```html
|
|
283
|
+
<a href="/dashboard" b-link>Dashboard</a>
|
|
284
|
+
```
|
|
357
285
|
|
|
286
|
+
---
|
|
358
287
|
|
|
288
|
+
## Programmatic Navigation
|
|
359
289
|
|
|
360
|
-
|
|
290
|
+
```js
|
|
291
|
+
history.pushState({}, '', '/success');
|
|
292
|
+
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
293
|
+
```
|
|
361
294
|
|
|
362
|
-
|
|
295
|
+
---
|
|
363
296
|
|
|
364
|
-
##
|
|
297
|
+
## Defining Components in JavaScript
|
|
365
298
|
|
|
366
|
-
|
|
299
|
+
You can also define components programmatically:
|
|
367
300
|
|
|
301
|
+
```js
|
|
302
|
+
Lego.define(
|
|
303
|
+
'counter-box',
|
|
304
|
+
`
|
|
305
|
+
<style>self { display:block }</style>
|
|
306
|
+
<button @click="count++">{{ count }}</button>
|
|
307
|
+
`,
|
|
308
|
+
{ count: 0 }
|
|
309
|
+
);
|
|
368
310
|
```
|
|
369
|
-
<body>
|
|
370
|
-
<nav>
|
|
371
|
-
<a href="/" b-link>Home</a>
|
|
372
|
-
<a href="/profile/123" b-link>Profile</a>
|
|
373
|
-
</nav>
|
|
374
311
|
|
|
375
|
-
|
|
376
|
-
<lego-router></lego-router>
|
|
377
|
-
</body>
|
|
312
|
+
---
|
|
378
313
|
|
|
379
|
-
|
|
314
|
+
## Initialization
|
|
380
315
|
|
|
381
|
-
|
|
316
|
+
LegoJS initializes automatically on `DOMContentLoaded`.
|
|
382
317
|
|
|
383
|
-
|
|
318
|
+
You usually do **not** need to call anything manually.
|
|
384
319
|
|
|
385
|
-
|
|
386
|
-
// Map the root path to 'home-page' block
|
|
387
|
-
Lego.route('/', 'home-page');
|
|
320
|
+
---
|
|
388
321
|
|
|
389
|
-
|
|
390
|
-
Lego.route('/about', 'about-page');
|
|
322
|
+
## Design Philosophy
|
|
391
323
|
|
|
392
|
-
|
|
324
|
+
LegoJS is intentionally small and opinionated:
|
|
393
325
|
|
|
394
|
-
|
|
326
|
+
* The DOM is the source of truth
|
|
327
|
+
* JavaScript objects are the state
|
|
328
|
+
* HTML stays HTML
|
|
329
|
+
* Complexity is avoided unless absolutely necessary
|
|
395
330
|
|
|
396
|
-
|
|
331
|
+
If you can explain your UI with plain objects and markup, LegoJS will feel natural.
|
|
397
332
|
|
|
398
|
-
|
|
333
|
+
---
|
|
399
334
|
|
|
400
|
-
|
|
401
|
-
Lego.route('/user/:id', 'user-profile');
|
|
402
|
-
Lego.route('/post/:category/:slug', 'blog-post');
|
|
335
|
+
## Single File Components (SFC)
|
|
403
336
|
|
|
404
|
-
|
|
337
|
+
LegoJS supports **Single File Components** using `.lego` files when using a build tool like Vite.
|
|
405
338
|
|
|
406
|
-
###
|
|
339
|
+
### .lego File Format
|
|
407
340
|
|
|
408
|
-
|
|
341
|
+
A `.lego` file contains three optional sections:
|
|
409
342
|
|
|
410
|
-
```
|
|
411
|
-
<template
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
</div>
|
|
343
|
+
```html
|
|
344
|
+
<template>
|
|
345
|
+
<!-- Your HTML markup with directives -->
|
|
346
|
+
<h1>{{ title }}</h1>
|
|
347
|
+
<button @click="count++">{{ count }}</button>
|
|
416
348
|
</template>
|
|
417
349
|
|
|
350
|
+
<script>
|
|
351
|
+
export default {
|
|
352
|
+
// Your component logic/state
|
|
353
|
+
title: 'Hello',
|
|
354
|
+
count: 0
|
|
355
|
+
}
|
|
356
|
+
</script>
|
|
357
|
+
|
|
358
|
+
<style>
|
|
359
|
+
/* Scoped CSS using self keyword */
|
|
360
|
+
self {
|
|
361
|
+
display: block;
|
|
362
|
+
padding: 1rem;
|
|
363
|
+
}
|
|
364
|
+
</style>
|
|
418
365
|
```
|
|
419
366
|
|
|
420
|
-
|
|
367
|
+
The component name is automatically derived from the filename (e.g., `sample-component.lego` β `<sample-component>`).
|
|
421
368
|
|
|
422
|
-
|
|
369
|
+
---
|
|
423
370
|
|
|
424
|
-
|
|
371
|
+
## Vite Plugin Setup
|
|
425
372
|
|
|
426
|
-
|
|
427
|
-
Lego.route('/admin', 'admin-dashboard', async (params, globals) => {
|
|
428
|
-
const isAuthorized = globals.user && globals.user.role === 'admin';
|
|
429
|
-
|
|
430
|
-
if (!isAuthorized) {
|
|
431
|
-
// Redirect to login or home
|
|
432
|
-
history.pushState({}, '', '/login');
|
|
433
|
-
// We must manually trigger the router check after pushState
|
|
434
|
-
Lego.init();
|
|
435
|
-
return false; // Prevent 'admin-dashboard' from loading
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
return true; // Proceed to load the component
|
|
439
|
-
});
|
|
373
|
+
### Installation
|
|
440
374
|
|
|
375
|
+
```bash
|
|
376
|
+
npm install lego-dom vite
|
|
441
377
|
```
|
|
442
378
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
To navigate without a full page refresh, use the `b-link` directive on anchor tags. This intercepts the click, updates the URL via `pushState`, and tells the Lego router to swap the view.
|
|
379
|
+
### Configuration
|
|
446
380
|
|
|
447
|
-
|
|
448
|
-
<!-- This will trigger the router logic -->
|
|
449
|
-
<a href="/dashboard" b-link>Go to Dashboard</a>
|
|
381
|
+
Create `vite.config.js`:
|
|
450
382
|
|
|
451
|
-
|
|
452
|
-
|
|
383
|
+
```js
|
|
384
|
+
import { defineConfig } from 'vite';
|
|
385
|
+
import legoPlugin from 'lego-dom/vite-plugin';
|
|
453
386
|
|
|
387
|
+
export default defineConfig({
|
|
388
|
+
plugins: [
|
|
389
|
+
legoPlugin({
|
|
390
|
+
componentsDir: './src/components', // Where to look for .lego files
|
|
391
|
+
include: ['**/*.lego'] // Glob patterns to match
|
|
392
|
+
})
|
|
393
|
+
]
|
|
394
|
+
});
|
|
454
395
|
```
|
|
455
396
|
|
|
456
|
-
|
|
397
|
+
### Usage
|
|
457
398
|
|
|
458
|
-
|
|
399
|
+
Create your components in `.lego` files:
|
|
459
400
|
|
|
460
401
|
```
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
|
|
402
|
+
src/
|
|
403
|
+
components/
|
|
404
|
+
my-button.lego
|
|
405
|
+
user-card.lego
|
|
406
|
+
main.js
|
|
407
|
+
index.html
|
|
469
408
|
```
|
|
470
409
|
|
|
471
|
-
|
|
410
|
+
In your `main.js`:
|
|
472
411
|
|
|
473
|
-
|
|
412
|
+
```js
|
|
413
|
+
import { Lego } from 'lego-dom/main.js';
|
|
414
|
+
import registerComponents from 'virtual:lego-components';
|
|
474
415
|
|
|
416
|
+
registerComponents();
|
|
475
417
|
```
|
|
476
|
-
// router-config.js
|
|
477
|
-
import './views/home.lego';
|
|
478
|
-
import './views/login.lego';
|
|
479
|
-
import './views/profile.lego';
|
|
480
|
-
|
|
481
|
-
export const setupRouter = () => {
|
|
482
|
-
Lego.route('/', 'home-view');
|
|
483
|
-
Lego.route('/login', 'login-view');
|
|
484
|
-
|
|
485
|
-
// Nested logic for clean organization
|
|
486
|
-
Lego.route('/profile/:username', 'profile-view', (params) => {
|
|
487
|
-
return !!params.username; // Basic validation
|
|
488
|
-
});
|
|
489
|
-
};
|
|
490
418
|
|
|
491
|
-
|
|
419
|
+
In your HTML:
|
|
492
420
|
|
|
493
|
-
|
|
421
|
+
```html
|
|
422
|
+
<my-button></my-button>
|
|
423
|
+
<user-card></user-card>
|
|
424
|
+
```
|
|
494
425
|
|
|
495
|
-
|
|
426
|
+
**Auto-discovery**: The Vite plugin automatically finds all `.lego` files and registers them with LegoJS!
|
|
496
427
|
|
|
497
|
-
|
|
428
|
+
---
|
|
498
429
|
|
|
499
|
-
|
|
430
|
+
## Two Usage Modes
|
|
500
431
|
|
|
501
|
-
**
|
|
432
|
+
LegoJS works in **two modes**:
|
|
502
433
|
|
|
503
|
-
|
|
434
|
+
### 1. Without Build Tooling
|
|
504
435
|
|
|
505
|
-
|
|
436
|
+
Include `main.js` directly and use `<template b-id>` or `Lego.define()`:
|
|
506
437
|
|
|
507
|
-
|
|
438
|
+
```html
|
|
439
|
+
<script src="node_modules/lego-dom/main.js"></script>
|
|
440
|
+
<template b-id="my-component">
|
|
441
|
+
<h1>Hello</h1>
|
|
442
|
+
</template>
|
|
443
|
+
```
|
|
508
444
|
|
|
509
|
-
|
|
445
|
+
### 2. With Vite (SFC)
|
|
510
446
|
|
|
511
|
-
|
|
447
|
+
Use `.lego` files that are auto-discovered and compiled:
|
|
512
448
|
|
|
513
|
-
|
|
449
|
+
```bash
|
|
450
|
+
npm run dev
|
|
451
|
+
```
|
|
514
452
|
|
|
515
|
-
|
|
453
|
+
Both modes use the same LegoJS runtime and support all the same features!
|
|
516
454
|
|
|
517
|
-
|
|
455
|
+
---
|
|
518
456
|
|
|
519
|
-
|
|
457
|
+
## Summary
|
|
520
458
|
|
|
521
|
-
|
|
459
|
+
* Install with `npm install lego-dom`
|
|
460
|
+
* Define components with `<template b-id>`
|
|
461
|
+
* Use `b-data` for state
|
|
462
|
+
* Use `{{ }}` for binding
|
|
463
|
+
* Use `@event` for logic
|
|
464
|
+
* Use `b-if`, `b-for`, and `b-sync` for structure
|
|
522
465
|
|
|
523
|
-
|
|
466
|
+
Thatβs it.
|